Приведение Int к Generic Enum в C #

86

Подобно Cast int to enum в C #, но мое перечисление является параметром Generic Type. Как лучше всего с этим справиться?

Пример:

private T ConvertEnum<T>(int i) where T : struct, IConvertible
{
    return (T)i;
}

Генерирует ошибку компилятора Cannot convert type 'int' to 'T'

Полный код выглядит следующим образом, где значение может содержать int или null.

private int? TryParseInt(string value)
{
    var i = 0;
    if (!int.TryParse(value, out i))
    {
        return null;
    }
    return i;
}

private T? TryParseEnum<T>(string value) where T : struct, IConvertible
{
    var i = TryParseInt(value);
    if (!i.HasValue)
    {
        return null;
    }

    return (T)i.Value;
}
csauve
источник
stackoverflow.com/questions/2745320/… - может помочь?
Солнечный
Последний ответ на stackoverflow.com/questions/1331739/… ближе к тому, что вы хотите. Но это все еще не умно. Я обычно использую для этого отражение, вы можете сделать код намного сильнее. На мой взгляд, Struct недостаточно ограничен, чтобы возиться с дженериками.
Тони Хопкинсон
1
Что-то, что не коробится: c-sharp-non-boxing-conversion-of-generic-enum-to-int
nawfal

Ответы:

121

Самый простой способ, который я нашел, - заставить компилятор работать, добавив приведение к object.

return (T)(object)i.Value;
Гуванте
источник
12
Если вам не нравится бокс: c-sharp-non-boxing-conversion-of-generic-enum-to-int
nawfal 08
5
Мы приводим enum к int, а не наоборот, как в вопросе So, который вы ссылаетесь. Кроме того, у этого вопроса нет решения.
MatteoSp,
Вы также можете просто выделить статический массив со значениями перечисления, а затем просто передать индекс, чтобы получить правильное перечисление. Это избавляет от необходимости выполнять кастинг. Пример (К этой концепции относятся только строки 11, 14 и 34): pastebin.com/iPEzttM4
Krythic 02
21

Вы должны уметь использовать Enum.Parseдля этого:

return (T)Enum.Parse(typeof(T), i.Value.ToString(), true);

В этой статье рассказывается о разборе общих перечислений для методов расширения:

Джеймс Джонсон
источник
@Guvante: Я думаю, что преобразовал значение в строку в своем примере. Вы предвидите, что это вызовет проблему?
Джеймс Джонсон
16

Вот очень быстрое решение, которое злоупотребляет тем фактом, что среда выполнения создает несколько экземпляров статических универсальных классов. Дайте волю своим демонам внутренней оптимизации!

Это действительно замечательно, когда вы читаете Enums из потока обычным образом. Объедините с внешним классом, который также кэширует базовый тип перечисления, и BitConverter, чтобы раскрыть потрясающие возможности.

void Main() 
{
    Console.WriteLine("Cast (reference): {0}", (TestEnum)5);
    Console.WriteLine("EnumConverter: {0}", EnumConverter<TestEnum>.Convert(5));
    Console.WriteLine("Enum.ToObject: {0}", Enum.ToObject(typeof(TestEnum), 5));

    int iterations = 1000 * 1000 * 100;
    Measure(iterations, "Cast (reference)", () => { var t = (TestEnum)5; });
    Measure(iterations, "EnumConverter", () => EnumConverter<TestEnum>.Convert(5));
    Measure(iterations, "Enum.ToObject", () => Enum.ToObject(typeof(TestEnum), 5));
}

static class EnumConverter<TEnum> where TEnum : struct, IConvertible
{
    public static readonly Func<long, TEnum> Convert = GenerateConverter();

    static Func<long, TEnum> GenerateConverter()
    {
        var parameter = Expression.Parameter(typeof(long));
        var dynamicMethod = Expression.Lambda<Func<long, TEnum>>(
            Expression.Convert(parameter, typeof(TEnum)),
            parameter);
        return dynamicMethod.Compile();
    }
}

enum TestEnum 
{
    Value = 5
}

static void Measure(int repetitions, string what, Action action)
{
    action();

    var total = Stopwatch.StartNew();
    for (int i = 0; i < repetitions; i++)
    {
        action();
    }
    Console.WriteLine("{0}: {1}", what, total.Elapsed);
}

Результаты на Core i7-3740QM с включенной оптимизацией:

Cast (reference): Value
EnumConverter: Value
Enum.ToObject: Value
Cast (reference): 00:00:00.3175615
EnumConverter: 00:00:00.4335949
Enum.ToObject: 00:00:14.3396366
Раиф Атеф
источник
2
Это действительно здорово, спасибо. Однако вы можете использовать Expression.ConvertCheckedвместо этого, чтобы числовое переполнение диапазона типа перечисления приводило к расширению OverflowException.
Дрю Ноукс,
Ваш пробег может отличаться, я запускал код на try.dot.net (blazor), и там EnumConverter <T> намного медленнее, чем альтернативы. Приведение к первому объекту было примерно в 6 раз медленнее, чем прямое приведение, но все же намного лучше, чем другие варианты.
Herman
0
public static class Extensions
    {
        public static T ToEnum<T>(this int param)
        {
            var info = typeof(T);
            if (info.IsEnum)
            {
                T result = (T)Enum.Parse(typeof(T), param.ToString(), true);
                return result;
            }

            return default(T);
        }
    }
Владимир Кургузов
источник