繁体   English   中英

如何将通用枚举转换为 int?

[英]How do I cast a generic enum to int?

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

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

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

试试这个,

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
                        ..........
                        ..........
        }
    }
}

您也可以先将您的值转换为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继承,则InvalidCastException为 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并且转换的性能不如拆箱。)


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

这个问题有很多答案。 我感兴趣的是哪个解决方案(int)enumValue的工作原理完全相同,哪个是最快的 因此,我使用 BenchmarkDotNet 编写了一个基准测试,并检查了所用方法的结果。

性能基准

方法 意思 错误 标准偏差 中位数 0代 已分配
Convert_ToInt32 20.4430 纳秒 0.1278 纳秒 0.1067 纳秒 20.4343 纳秒 0.0003 48乙
CastTo_Object_Int 8.3970 纳秒 0.0664 纳秒 0.0621纳秒 8.3929 纳秒 0.0002 24乙
ByPointers_Switch_Byte 0.4515 纳秒 0.0035纳秒 0.0029纳秒 0.4512 纳秒 - -
ByPointers_Switch_SByte 0.4664 纳秒 0.0069纳秒 0.0064 纳秒 0.4687 纳秒 - -
CompiledLambdaFunc 0.2236 纳秒 0.0055 纳秒 0.0051纳秒 0.2230纳秒 - -
不安全的 0.0045纳秒 0.0050纳秒 0.0047纳秒 0.0034纳秒 - -
获取哈希码 0.0034纳秒 0.0036 纳秒 0.0034纳秒 0.0019纳秒 - -
ByPointers_DirectInt 0.0030纳秒 0.0051纳秒 0.0048纳秒 0.0000纳秒 - -
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22000
AMD Ryzen 9 5900HX with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK=6.0.300

(int)enumValue的一致性

方法 整数枚举 整数枚举 长枚举 ULong枚举 字节枚举 S字节枚举
Convert_ToInt32 ✓ 相同 投掷 投掷 投掷 ✓ 相同 ✓ 相同
CastTo_Object_Int ✓ 相同 投掷 投掷 投掷 投掷 投掷
ByPointers_Switch_Byte ✓ 相同 ✓ 相同 ✓ 相同 ✓ 相同 ✓ 相同 不同*
ByPointers_Switch_SByte ✓ 相同 ✓ 相同 ✓ 相同 ✓ 相同 不同* ✓ 相同
CompiledLambdaFunc ✓ 相同 ✓ 相同 ✓ 相同 ✓ 相同 ✓ 相同 ✓ 相同
不安全的 ✓ 相同 ✓ 相同 ✓ 相同 ✓ 相同 ✓ 相同 ✓ 相同
获取哈希码 ✓ 相同 ✓ 相同 不同 不同 ✓ 相同 ✓ 相同
ByPointers_DirectInt ✓ 相同 ✓ 相同 ✓ 相同 ✓ 相同 ✓ 相同 ✓ 相同
Legend:
✓ same:   gives the same results
throws:   throws an exception at least for some cases
differs:  gives different results at least for some cases
differs*: gives different results in Debug, same results in Release

关于 differs differs*问题:关于发布模式中的*(byte*)(&sbyteValue)不一致。

结果

两种方法既最快又与(int)enumValue一致:

public static int Unsafe_As<TEnum>(TEnum enumValue)
    where TEnum : struct, Enum
{
    return Unsafe.As<TEnum, int>(ref enumValue);
}

public static unsafe int ByPointers_DirectInt<TEnum>(TEnum enumValue)
    where TEnum : unmanaged, Enum
{
    return *(int*)(&enumValue);
}

对于TEnum: int, uint, long or ulong这些方法应该是安全的并且工作良好。

注意:对于TEnum: byte, sbyte, short or ushort这些方法访问的 memory 多于 TEnum 的本机大小。 如果额外的 memory 都是'0',那么我们得到正确的结果。 但如果不是,那么结果最终会被破坏。

我试图做一个失败的例子,但我失败了。 欢迎任何人的建议或解释。

Fastest among safe:下一个最快的方法给出与(int)enumValue完全相同的结果,因为它是此转换的动态编译版本。 如果我们考虑完全安全,那么CompiledLambdaFunc赢家

public static int CompiledLambdaFunc<TEnum>(TEnum value)
    where TEnum : struct, Enum
{
    return StaticGenericCache<TEnum>.TheFunc(value);
}

private static class StaticGenericCache<T>
    where T : struct, Enum
{
    public static Func<T, int> TheFunc = GenerateFunc<T>();
}

private static Func<TEnum, int> GenerateFunc<TEnum>()
    where TEnum : struct, Enum
{
    var inputParameter = Expression.Parameter(typeof(TEnum));

    var body = Expression.Convert(inputParameter, typeof(int)); // means: (int)input;

    var lambda = Expression.Lambda<Func<TEnum, int>>(body, inputParameter);

    var func = lambda.Compile();

    return func;
}

基准代码

[MemoryDiagnoser]
public class EnumToIntBenchmark
{
    [Benchmark]
    public int Convert_ToInt32() => Methods.Convert_ToInt32(SpecificEnum.TheValue);

    [Benchmark]
    public int CastTo_Object_Int() => Methods.CastTo_Object_Int(SpecificEnum.TheValue);

    [Benchmark]
    public int ByPointers_Switch_Byte() => Methods.ByPointers_Switch_Byte(SpecificEnum.TheValue);

    [Benchmark]
    public int ByPointers_Switch_SByte() => Methods.ByPointers_Switch_SByte(SpecificEnum.TheValue);

    [Benchmark]
    public int CompiledLambdaFunc() => Methods.CompiledLambdaFunc(SpecificEnum.TheValue);

    [Benchmark]
    public int Unsafe_As() => Methods.Unsafe_As(SpecificEnum.TheValue);

    [Benchmark]
    public new int GetHashCode() => Methods.GetHashCode(SpecificEnum.TheValue);

    [Benchmark]
    public int ByPointers_DirectInt() => Methods.ByPointers_DirectInt(SpecificEnum.TheValue);

    private enum SpecificEnum
    {
        None = 0,
        TheValue,
    }

    public static class Methods
    {
        public static int Convert_ToInt32<TEnum>(TEnum value)
            where TEnum : struct, Enum
        {
            return Convert.ToInt32(value);
        }

        public static int CastTo_Object_Int<TEnum>(TEnum value)
            where TEnum : struct, Enum
        {
            return (int)(object)value;
        }

        public static unsafe int ByPointers_Switch_Byte<TEnum>(TEnum enumValue)
            where TEnum : unmanaged, Enum
        {
            switch (sizeof(TEnum))
            {
                case 1:  return *(byte*)(&enumValue);
                case 2:  return *(short*)(&enumValue);
                case 4:  return *(int*)(&enumValue);
                case 8:  return (int)*(long*)(&enumValue);
                default: throw new NotImplementedException($"Not implemented for size: {sizeof(TEnum)}");
            }
        }

        public static unsafe int ByPointers_Switch_SByte<TEnum>(TEnum enumValue)
            where TEnum : unmanaged, Enum
        {
            switch (sizeof(TEnum))
            {
                case 1:  return *(sbyte*)(&enumValue);
                case 2:  return *(short*)(&enumValue);
                case 4:  return *(int*)(&enumValue);
                case 8:  return (int)*(long*)(&enumValue);
                default: throw new NotImplementedException($"Not implemented for size: {sizeof(TEnum)}");
            }
        }

        public static unsafe int ByPointers_DirectInt<TEnum>(TEnum enumValue)
            where TEnum : unmanaged, Enum
        {
            return *(int*)(&enumValue);
        }

        public static int Unsafe_As<TEnum>(TEnum enumValue)
            where TEnum : struct, Enum
        {
            return Unsafe.As<TEnum, int>(ref enumValue);
        }

        public static int GetHashCode<TEnum>(TEnum value)
            where TEnum : struct, Enum
        {
            return value.GetHashCode();
        }

        public static int CompiledLambdaFunc<TEnum>(TEnum value)
            where TEnum : struct, Enum
        {
            return StaticGenericCache<TEnum>.TheFunc(value);
        }

        private static class StaticGenericCache<T>
            where T : struct, Enum
        {
            public static Func<T, int> TheFunc = GenerateFunc<T>();
        }

        private static Func<TEnum, int> GenerateFunc<TEnum>()
            where TEnum : struct, Enum
        {
            var inputParameter = Expression.Parameter(typeof(TEnum));

            var body = Expression.Convert(inputParameter, typeof(int)); // means: (int)input;

            var lambda = Expression.Lambda<Func<TEnum, int>>(body, inputParameter);

            var func = lambda.Compile();

            return func;
        }
    }
}

一致性校验表代码

class Program
{
    static void Main()
    {
        var table = GenerateConsistencyTable();

        Console.WriteLine(table);
    }

    private static string GenerateConsistencyTable()
    {
        var sb = new StringBuilder();

        sb.AppendLine(GenerateHeader());
        sb.AppendLine(GenerateUnderHeader());

        foreach (var methodName in _methodNames)
        {
            sb.AppendLine(CheckAllEnumsForMethod(methodName));
        }

        return sb.ToString().Trim();
    }

    private static readonly string[] _methodNames = new string[]
    {
        nameof(EnumToIntBenchmark.Methods.Convert_ToInt32),
        nameof(EnumToIntBenchmark.Methods.CastTo_Object_Int),
        nameof(EnumToIntBenchmark.Methods.ByPointers_Switch_Byte),
        nameof(EnumToIntBenchmark.Methods.ByPointers_Switch_SByte),
        nameof(EnumToIntBenchmark.Methods.CompiledLambdaFunc),
        nameof(EnumToIntBenchmark.Methods.Unsafe_As),
        nameof(EnumToIntBenchmark.Methods.GetHashCode),
        nameof(EnumToIntBenchmark.Methods.ByPointers_DirectInt),
    };

    private static readonly Type[] _allEnumTypes = new Type[] { typeof(IntEnum), typeof(UIntEnum), typeof(LongEnum), typeof(ULongEnum), typeof(ByteEnum), typeof(SByteEnum) };

    private static string GenerateHeader()
    {
        var line = $"| {"Method",30} ";

        foreach (var enumType in _allEnumTypes)
        {
            line += $"| {enumType.Name,10} ";
        }

        line += '|';

        return line;
    }

    private static string GenerateUnderHeader()
    {
        var line = '|' + new string('-', 32);

        foreach (var enumType in _allEnumTypes)
        {
            line += '|' + new string('-', 11) + ':';
        }

        line += '|';

        return line;
    }

    private static string CheckAllEnumsForMethod(string methodName)
    {
        var line = $"| {methodName,30} ";

        foreach (var enumType in _allEnumTypes)
        {
            line += $"| {CheckMethodAndEnum(enumType, methodName),10} ";
        }

        line += '|';

        return line;
    }

    private static string CheckMethodAndEnum(Type enumType, string methodName)
    {
        var methodsClassType = typeof(EnumToIntBenchmark.Methods);
        var methodInfoGeneric = methodsClassType.GetMethods().Single(method => method.Name == methodName && method.IsGenericMethodDefinition);
        var methodInfoSpecific = methodInfoGeneric!.MakeGenericMethod(enumType);

        var funcType = typeof(Func<,>).MakeGenericType(enumType, typeof(int));
        var methodFuncDelegate = Delegate.CreateDelegate(funcType, methodInfoSpecific);
        var methodFunc = Convert.ChangeType(methodFuncDelegate, funcType);

        var checkMethodGeneric = typeof(Program).GetMethod(nameof(CheckMethodAndEnumCore), BindingFlags.Static | BindingFlags.NonPublic);
        var checkMethod = checkMethodGeneric!.MakeGenericMethod(enumType);

        return (string)checkMethod.Invoke(null, new object?[] { methodFunc })!;
    }

    private static string CheckMethodAndEnumCore<TEnum>(Func<TEnum, int> method)
        where TEnum : struct, Enum
    {
        bool anyIsDifferent = false;

        try
        {
            var allEnumValues = Enum.GetValues<TEnum>();

            foreach (var enumValue in allEnumValues)
            {
                var expected = RealCastToInt(enumValue);
                var actual = method(enumValue);

                if (expected != actual)
                {
                    anyIsDifferent = true;
                }
            }
        }
        catch (Exception e)
        {
            return "throws";
        }

        return anyIsDifferent ? "differs" : "\u2713 same";
    }

    private static int RealCastToInt<TEnum>(TEnum enumValue)
        where TEnum : struct, Enum
    {
        switch (enumValue)
        {
            case IntEnum typedValue:   return (int)typedValue;
            case LongEnum typedValue:  return (int)typedValue;
            case UIntEnum typedValue:  return (int)typedValue;
            case ULongEnum typedValue: return (int)typedValue;
            case ByteEnum typedValue:  return (int)typedValue;
            case SByteEnum typedValue: return (int)typedValue;
            default:                   throw new NotImplementedException($"Not implemented for type: {typeof(TEnum)}");
        }
    }

    enum IntEnum : int
    {
        None = 0,
        One = 1,
        MinusOne = -1,
        MinValue = int.MinValue,
        MaxValue = int.MaxValue,
    }

    enum LongEnum : long
    {
        None = 0,
        One = 1,
        MinusOne = -1,
        MinValue = long.MinValue,
        MaxValue = long.MaxValue,
    }

    enum UIntEnum : uint
    {
        None = 0,
        One = 1,
        MinValue = uint.MinValue,
        MaxValue = uint.MaxValue,
    }

    enum ULongEnum : ulong
    {
        None = 0,
        One = 1,
        MinValue = ulong.MinValue,
        MaxValue = ulong.MaxValue,
    }

    enum ByteEnum : byte
    {
        None = 0,
        One = 1,
        MinValue = byte.MinValue,
        MaxValue = byte.MaxValue,
    }

    enum SByteEnum : sbyte
    {
        None = 0,
        One = 1,
        MinusOne = -1,
        MinValue = sbyte.MinValue,
        MaxValue = sbyte.MaxValue,
    }
}

这个适用于任何基础类型

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())));
}

这正在为我们做一切。

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

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

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

是 dotnet 的 GitHub 页面上提出此问题的问题的链接,如果您想了解更多信息,一些开发人员详细介绍了这种方法。

您可以为此滥用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. 利珀特

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

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;
}

我很惊讶你的代码完全有效。 Enum.GetValues返回一个整数数组 - 这是您要查找的值。 而且,正如其他人所提到的, 您不能将泛型限制为 enum

相反,您可能应该将您的Description方法作为常规静态方法而不是扩展方法来调用。

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

由于 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);

}

使用 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());
}

如果您将通用 T 限制为 Enum 使用

where T: Enum

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

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

这似乎是最简单的解决方案,只要您能保证 T 将是 Enum。

只需先将通用 T 转换为对象

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

而已。

试试这个:(假设 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()));
    }
}

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM