Как я могу привести общий enum к 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
,
Тем не менее, я хочу бросить obj
так же, как вы бы перечислить его индекс, как это (int)obj
, но я получаю сообщение о том, что не могу преобразовать T в int. Есть какой-либо способ сделать это?
14 ответов
Попробуй это,
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
приведение к 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
он будет использовать IConvertible
Интерфейс int32 для преобразования несовместимых типов.
public static int EnumToInt<TValue>(this TValue value) where TValue : Enum
=> Convert.ToInt32(value);
Просто знайте, что преобразование uint
в int
и подписанные / неподписанные пары могут вызвать непреднамеренное поведение. (Бокс для IConvertible
и преобразование менее производительно, чем просто распаковка.)
Я рекомендую создать метод для каждого базового типа перечисления, чтобы обеспечить возвращение правильного результата.
На этот вопрос есть много ответов. Меня интересовало, какое решение работает точно так же, как и самое быстрое. Поэтому я написал бенчмарк с помощью BenchmarkDotNet и проверил также результаты использованных методов.
Тест производительности
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*
: о*(byte*)(&sbyteValue)
несоответствие в режиме Release.
Полученные результаты
Два метода были самыми быстрыми и совместимыми с:
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
эти методы обращаются к большему объему памяти, чем собственный размер TEnum. Если вся дополнительная память равна «0», то мы получаем правильные результаты. Но если нет, то результаты в конечном итоге испорчены.
Я пытался сделать неудачный пример, но у меня не получилось. Любое предложение или объяснение приветствуется.
Самый быстрый среди безопасных: следующий самый быстрый метод дает точно такие же результаты, как(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,
}
}
Если вы ориентируетесь на.NET Core, вы можете использовать Unsafe.As<TFrom, TTo>
в System.Runtime.CompilerServices
пространство имен, как описано в MSDN. Преимущество здесь в том, что бокс не будет выполняться, что является единственной реальной стоимостью производительности в других ответах здесь.
private static int EnumToInt<TEnum>(TEnum enumValue) where TEnum : Enum
{
return Unsafe.As<TEnum, int>(enumValue);
}
Обратите внимание, что этот подход страдает от проблемы безопасности, как и другие существующие ответы: нет гарантии, что данное перечисление является совместимым int
type, что, вероятно, является той же причиной, по которой эта функция не встроена. Если вы используете этот подход внутри, где вы можете быть уверены, что любое переданное ему перечисление является совместимым типом, то это, вероятно, наиболее эффективный подход.
Вот ссылка на проблему на странице dotnet GitHub, где эта проблема была поднята, и некоторые разработчики немного подробно остановились на этом подходе, если вы хотите узнать больше.
Вот мое решение для 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;
}
Этот работает с любым базовым типом
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())));
}
Который делает все для нас.
Можете ли вы злоупотребить 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()
по своему дизайну полезен только для одного: поместить объект в хеш-таблицу. Отсюда и название." - Э. Липперт
Вот более простой способ.
Поскольку 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);
}
Если вы переустанавливаете общий 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;
Вот и все.
Я удивлен, что твой код работает вообще. Enum.GetValues
возвращает массив целых чисел - это значения, которые вы ищете. И, как уже упоминали другие, вы не можете ограничить свои дженерики перечислением.
Вместо этого вам, вероятно, следует позвонить Description
Метод как обычный статический метод, а не метод расширения.
Немного опоздал на вечеринку, но с помощью 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());
}
Попробуйте это: (при условии, что 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()));
}
}
Чтобы расширить ответ Яна относительно значений дженериков и перечислений:
void MyFunc<T>(T value)
{
Type t = typeof(T);
if(t.IsEnum)
{
int valueAsInt = value.GetHashCode(); // returns the integer value
}
}