Prześlij Int do Generic Enum w C #


85

Podobnie jak rzutowanie int na wyliczenie w C #, ale moje wyliczenie jest parametrem typu ogólnego. Jak najlepiej sobie z tym poradzić?

Przykład:

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

Generuje błąd kompilatora Cannot convert type 'int' to 'T'

Pełny kod jest następujący, gdzie value może zawierać int lub 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;
}


Ostatnia odpowiedź na stackoverflow.com/questions/1331739/… , jest bliższa temu, czego chcesz. Jednak nadal nie jest to sprytne. Zwykle używam do tego refleksji, możesz znacznie wzmocnić kod. Struct nie jest na tyle restrykcyjny, aby moim zdaniem bawić się lekami generycznymi.
Tony Hopkinson

1
Coś, co nie boxing
nawfal

Odpowiedzi:


121

Najprostszym sposobem, jaki znalazłem, jest wymuszenie ręki kompilatora, dodając rzut do object.

return (T)(object)i.Value;


5
Rzucamy wyliczenie na int, a nie odwrotnie, jak w linku So question you. Również to pytanie nie ma rozwiązania.
MatteoSp

Możesz również po prostu przydzielić tablicę statyczną z wartościami wyliczenia, a następnie po prostu przekazać indeks, aby pobrać poprawne wyliczenie. Oszczędza to konieczności wykonywania jakichkolwiek odlewów. Przykład (tylko wiersze 11, 14 i 34 odnoszą się do tej koncepcji): pastebin.com/iPEzttM4
Krythic


16

Oto bardzo szybkie rozwiązanie, które wykorzystuje fakt, że środowisko wykonawcze tworzy wiele instancji statycznych klas ogólnych. Uwolnij swoje wewnętrzne demony optymalizacji!

To naprawdę świeci, gdy czytasz wyliczenia ze strumienia w ogólny sposób. Połącz z zewnętrzną klasą, która również buforuje podstawowy typ wyliczenia i BitConverter, aby uwolnić niesamowite.

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

Wyniki dla Core i7-3740QM z włączonymi optymalizacjami:

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
To jest naprawdę miłe, dzięki. Możesz jednak chcieć użyć Expression.ConvertCheckedzamiast tego, aby przepełnienie liczbowe zakresu typu wyliczenia skutkowało rozszerzeniem OverflowException.
Drew Noakes

Twój przebieg może się różnić, uruchomiłem kod na try.dot.net (blazor) i tam EnumConverter <T> jest znacznie wolniejszy niż alternatywy. Pierwsze rzutowanie na obiekt było około 6 razy wolniejsze niż bezpośrednie, ale wciąż znacznie lepsze niż inne opcje.
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);
        }
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.