【问题标题】:How do I cast a generic enum to int?如何将通用枚举转换为 int?
【发布时间】:2013-06-02 08:53:58
【问题描述】:

我有一个看起来像这样的小方法:

public void SetOptions<T>() where T : Enum
{
    int i = 0;
    foreach (T obj in Enum.GetValues(typeof(T)))
    {
        if (i == 0)
            DefaultOption = new ListItem(obj.Description(), obj.ToString());
        i++;
        DropDownList.Items.Add(new ListItem(obj.Description(), obj.ToString()));
    }
}

基本上,我从一个枚举中填充一个下拉列表。 Description()实际上是枚举的扩展方法,所以T绝对是enum

但是,我想将 obj 转换为像 (int)obj 这样的任何枚举到它的索引,但我收到一个错误,说我无法将 T 转换为 int。有没有办法做到这一点?

【问题讨论】:

  • enum 不能是通用约束的一部分。
  • 您到底想要完成什么?使用枚举类型的值填充下拉列表的值?
  • 我有许多子用户控件,它们都需要填充基本下拉列表。下拉列表位于基本用户控件上。以前,列表是在每个子控件中构建的,然后传递给基本用户控件,因此不需要泛型。但是希望只传递枚举类型并让基本控件完成其余的工作。
  • enum 随着 Visual Studio v15.7 和 C# 7.3 的发布,自 2018 年 5 月 7 日起,可以成为泛型类型约束的一部分。 stackoverflow.com/a/28527552/88409不过,这个演员阵容可能还是不行,哈哈。

标签: c# enums


【解决方案1】:

您也可以先将值转换为object,然后再转换为int

C# 7.3 及以上版本

使用Enum 通用约束。

public static int EnumToInt<TValue>(this TValue value) where TValue : Enum
    => (int)(object)value;

低于 C# 7.3

没有Enum 通用约束。

public static int EnumToInt<TValue>(this TValue value)  where TValue : struct, IConvertible
{
    if(!typeof(TValue).IsEnum)
    {
        throw new ArgumentException(nameof(value));
    }

    return (int)(object)value;
}

如果您的枚举继承自其他类型,例如从 byte 继承,则强制转换为 int 将抛出 InvalidCastException

您可以检查枚举的基类型是否为整数。

public static int EnumToInt<TValue>(this TValue value) where TValue : Enum
{
    if (!typeof(int).IsAssignableFrom(Enum.GetUnderlyingType(typeof(TValue))))
        throw new ArgumentException(nameof(TValue));

    return (int)(object)value;
}

或者如果你使用Convert.ToInt32,它会使用int32的IConvertible接口来转换不兼容的类型。

public static int EnumToInt<TValue>(this TValue value) where TValue : Enum
    => Convert.ToInt32(value);

请注意,将 uint 转换为 int 和有符号/无符号对可能会导致意外行为。 (装箱到IConvertible 并且转换的性能不如拆箱。)


我建议为每个枚举基类型创建一个方法,以确保返回正确的结果。

【讨论】:

  • 使用 C# 7.3 方法我有时会收到InvalidCastException。 VS 的即时窗口显示“无法将类型 'TValue' 转换为 'int'”,这很奇怪。调试器窗口显示内存中的TValue value 值被键入为枚举。
  • @Dai 我已经更新了我的答案以反映您的问题。
  • 您可以使用(TEnum)(ValueType)value 来避免转换为object
  • @Ryan ValueType 也是类,所以无论如何它都不会避免拳击。
【解决方案2】:

试试这个,

public void SetOptions<T>()
{
    Type genericType = typeof(T);
    if (genericType.IsEnum)
    {
        foreach (T obj in Enum.GetValues(genericType))
        {
            Enum test = Enum.Parse(typeof(T), obj.ToString()) as Enum;
            int x = Convert.ToInt32(test); // x is the integer value of enum
                        ..........
                        ..........
        }
    }
}

【讨论】:

  • 您还可以添加 where T : struct, IComparable 作为约束,这样只能传入结构(如数字和枚举),只是为了限制它
  • 如果底层类型不同于int则不工作
  • Convert.ToInt32() 有什么诀窍。
【解决方案3】:

如果您的目标是 .NET Core,则可以在 System.Runtime.CompilerServices 命名空间中使用 Unsafe.As&lt;TFrom, TTo&gt;,如 MSDN 中所述。这里的优点是不会进行装箱,这是此处其他答案中唯一真正的性能成本。

private static int EnumToInt<TEnum>(TEnum enumValue) where TEnum : Enum
{
    return Unsafe.As<TEnum, int>(enumValue);
}

请注意,这种方法与其他现有答案一样存在安全问题:不能保证给定的枚举是兼容的 int 类型,这可能与此功能未内置的原因相同。如果您在内部使用这种方法,并且可以确保传递给它的任何枚举都是兼容类型,那么这可能是最有效的方法。

Here 是 dotnet 的 GitHub 页面上提出此问题的问题的链接,如果您想了解更多信息,一些开发人员对此方法进行了详细说明。

【讨论】:

    【解决方案4】:

    这是我针对 C# 7.3 及更高版本的解决方案。与 OP 的问题不完全匹配,但可能对从谷歌找到这个的人有用。与其他答案相比的主要优点是它返回一个 ulong,这意味着任何允许的枚举类型都适合它。我还为此和其他一些答案制作了机器代码的comparison。是的,我很无聊,想进行一些过早的优化。

    private static unsafe ulong EnumAsUInt64<T>(T item) where T : unmanaged, Enum
    {
        ulong x;
        if (sizeof(T) == 1)
            x = *(byte*)(&item);
        else if (sizeof(T) == 2)
            x = *(ushort*)(&item);
        else if (sizeof(T) == 4)
            x = *(uint*)(&item);
        else if (sizeof(T) == 8)
            x = *(ulong*)(&item);
        else
            throw new ArgumentException("Argument is not a usual enum type; it is not 1, 2, 4, or 8 bytes in length.");
        return x;
    }
    

    【讨论】:

      【解决方案5】:

      这个适用于任何底层类型

      Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()))
      

      例如,当您想向SqlCommand 添加一个值时,它将枚举转换为0,并且您必须将其显式转换为匹配类型。但是我们可以编写如下扩展:

      public static void AddEnum(this SqlParameterCollection parameters, string parameterName, Enum value)
      {
          parameters.AddWithValue(parameterName, Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType())));
      }
      

      它为我们做一切。

      【讨论】:

        【解决方案6】:

        你能滥用GetHashCode吗?

        public enum MyEnum
        {
          Foo = 100,
          Bar = 200,
          Fizz = 0
        }
        
        static void Main(string[] args)
        {
          var i1 = MyEnum.Foo.GetHashCode();  // i1 = 100
          var i2 = MyEnum.Bar.GetHashCode();  // i2 = 200
          var i3 = MyEnum.Fizz.GetHashCode(); // i3 = 0
        }
        

        请注意:“GetHashCode() 按设计仅用于一件事:将对象放入哈希表中。因此得名。” - E. Lippert

        【讨论】:

        • 不保证GetHashCode 总是返回底层整数,或者它的实现在.NET 版本之间保持不变。也就是说,我不明白他们为什么会改变它。
        • GetHashCode() 用作字典键而不是枚举的 int 转换是否安全?
        • @noio GetHashCode() 是“按设计仅对一件事有用:将对象放入哈希表中”(said E. Lippert himself)(这使我自己的答案无效)我想这是安全的,但我不会用它来创建用于字典的键:为什么不按原样使用枚举?
        • 当 dict 键是泛型类型 (Dictionary&lt;TEnum, string&gt;) 时,Boxing 存在问题,受泛型类定义中的class MyClass&lt;TEnum&gt; where TEnum: struct, IComparable 约束(我知道这是一个枚举,但不是 which 枚举)。因此,我创建了一个 Dictionary&lt;int, string&gt; 并在各处手动操作 myDictionary[myEnumValue.GetHashCode()]
        • @noio 如果您不知道哪个枚举,那么您也无法知道枚举值不会发生冲突,因为不同的枚举可以使用相同的数值。我建议使用封闭类型(即MyClass&lt;SomeState&gt;)作为字典键 - 或者 - 如果您定义所有枚举并为每个枚举指定特定范围,您可以考虑将它们合并为一个。
        【解决方案7】:

        这里有一个更简单的方法。

        由于 Enum 实现了 IConvertible,我们可以使用 ToInt32(..)。

        int? value = (enumCandidate as IConvertible)?.ToInt32(CultureInfo.InvariantCulture.Numberformat);
        

        或者,如果您想要通用枚举的通用方法:

        public static int GetEnumValue<T>(T inputEnum) where T: struct, IConvertible
        {
            Type t = typeof(T);
            if (!t.IsEnum)
            {
                throw new ArgumentException("Input type must be an enum.");
            }
        
            return inputEnum.ToInt32(CultureInfo.InvariantCulture.NumberFormat);
        
        }
        

        或者更笼统的:

        public static int GetEnumValue(object enumInput)
        {
            Type t = enumInput.GetType();
            if (!t.IsEnum)
            {
                throw new ArgumentException("Input type must be an enum.");
            }
        
            return ((IConvertible)inputEnum).ToInt32(CultureInfo.InvariantCulture.NumberFormat);
        
        }
        

        【讨论】:

          【解决方案8】:

          如果你将泛型 T 限制为枚举使用

          where T: Enum
          

          然后你可以使用下面的单行

          public static int GetIndexFromEnum<T>(T enumValue) where T : Enum {
              int index = Convert.ToInt32(enumValue);
              return index;
          }
          

          这似乎是最简单的解决方案,只要你能保证 T 将是一个枚举。

          【讨论】:

            【解决方案9】:

            我很惊讶你的代码能正常工作。 Enum.GetValues 返回一个整数数组 - 这是您要查找的值。而且,正如其他人所提到的,you can't constrain your generics to an enum

            相反,您应该将Description 方法称为常规静态方法,而不是扩展方法。

            【讨论】:

            • 我不明白这个答案。你能详细说明一下吗..?
            • 他的方法不起作用,因为public void SetOptions&lt;T&gt;() where T : Enum根本不会编译
            • 我现在只是在构建方法,所以我还没有编译,因为我遇到了转换问题。
            • @IlyaIvanov:我可以理解该约束的问题,但我不明白答案是如何回答问题的..
            • C# 即将推出将泛型类型限制为枚举的功能。
            【解决方案10】:

            先将泛型 T 转换为对象

            T value;
            int int_value = (int)(object)value;
            

            就是这样。

            【讨论】:

            • 这与两年前@NtFreX 的回答非常相似,只是它们提供了更多解释,并解决了如何处理为早期版本的 C# 编写代码的情况。
            【解决方案11】:

            使用 LINQ 可以优雅地完成:

            public static void SetOptions<T>(this DropDownList dropDownList)
            {
                if (!typeof(T).IsEnum)
                {
                    throw new ArgumentException("Type must be an enum type.");
                }
            
                dropDownList.Items.AddRange(Enum
                    .GetValues(typeof(T))
                    .Cast<Enum>()
                    .Select(x => new ListItem(x.ToString(), Convert.ToInt32(x).ToString()))
                    .ToArray());
            }
            

            【讨论】:

              【解决方案12】:

              试试这个:(假设 TEnum 有一个从 0 到 n 的编号)

              public void SetOptions<TEnum>() where TEnum : Enum
              {
                  foreach (TEnum obj in Enum.GetValues(typeof(TEnum)))
                  {
                      var i = (int)(object)obj;
                      if (i == 0) DefaultOption = new ListItem(obj.Description(), obj.ToString());
                      DropDownList.Items.Add(new ListItem(obj.Description(), obj.ToString()));
                  }
              }
              

              【讨论】:

                【解决方案13】:

                扩展 Jan 关于泛型和枚举值的答案:

                void MyFunc<T>(T value)
                {
                    Type t = typeof(T);
                    if(t.IsEnum)
                    {
                        int valueAsInt = value.GetHashCode(); // returns the integer value
                    }
                }
                

                【讨论】:

                • 这不是假设Enum.GetUnderlyingType() 实际上是int。如果您出于某种原因碰巧使用public enum : long { },您将把一个 8 字节的值压缩成一个 4 字节的值...
                猜你喜欢
                • 2010-09-06
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-12-30
                • 1970-01-01
                相关资源
                最近更新 更多