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;
}
csauve
źródło
stackoverflow.com/questions/2745320/ ... - może pomóc?
Słoneczny
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;
Guvante
źródło
12
Jeśli nie lubisz boksu: c-sharp-non-boxing-conversion-of-generic-enum-to-int
nawfal
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
20

Powinieneś być w stanie użyć Enum.Parsedo tego:

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

W tym artykule omówiono analizowanie ogólnych wyliczeń pod kątem metod rozszerzania:

James Johnson
źródło
@Guvante: Myślę, że w moim przykładzie przekonwertowałem wartość na ciąg. Czy przewidujesz, że spowoduje to problem?
James Johnson
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
Raif Atef
źródło
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);
        }
    }
Vladimir Kurguzov
źródło