[英]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
。
使用Enum
通用约束。
public static int EnumToInt<TValue>(this TValue value) where TValue : Enum
=> (int)(object)value;
没有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.