Conversión de Int a Genérico Enum en C #

84

Similar a Cast int to enum en C # pero mi enum es un parámetro de tipo genérico. ¿Cuál es la mejor manera de manejar esto?

Ejemplo:

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

Genera error del compilador Cannot convert type 'int' to 'T'

El código completo es el siguiente, donde el valor puede contener int o 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
fuente
stackoverflow.com/questions/2745320/… - ¿Podría ayudar?
Soleado
La última respuesta en stackoverflow.com/questions/1331739/… está más cerca de lo que desea. Sin embargo, todavía no es inteligente. Tiendo a usar la reflexión para esto, puedes hacer que el código sea mucho más fuerte. Struct no es lo suficientemente retrictivo como para hacer que valga la pena jugar con genéricos en mi opinión.
Tony Hopkinson

Respuestas:

120

La forma más sencilla que he encontrado es forzar la mano del compilador agregando un molde a object.

return (T)(object)i.Value;
Guvante
fuente
12
Si no le gusta el boxeo: c-sharp-non-boxing-conversion-of-generic-enum-to-int
nawfal
5
Estamos lanzando enum a int, no al contrario, como en el enlace So question you. Además, esa pregunta no tiene solución.
MatteoSp
También puede asignar una matriz estática con los valores de enumeración y luego simplemente pasar el índice para recuperar la enumeración correcta. Esto evita tener que hacer cualquier tipo de casting. Ejemplo (solo las líneas 11, 14 y 34 son relevantes para este concepto): pastebin.com/iPEzttM4
Krythic
20

Debería poder usar Enum.Parsepara esto:

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

Este artículo habla sobre el análisis de enumeraciones genéricas para métodos de extensión:

James Johnson
fuente
@Guvante: Creo que convertí el valor en una cadena en mi ejemplo. ¿Prevé que esto cause un problema?
James Johnson
16

Aquí hay una solución muy rápida que abusa del hecho de que el tiempo de ejecución crea múltiples instancias de clases genéricas estáticas. ¡Desata tus demonios internos de optimización!

Esto realmente brilla cuando estás leyendo Enums de una transmisión de forma genérica. Combine con una clase externa que también almacena en caché el tipo subyacente de la enumeración y un BitConverter para dar rienda suelta a lo impresionante.

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

Resultados en Core i7-3740QM con optimizaciones habilitadas:

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
fuente
2
Esto es muy lindo, gracias. Puede que te guste usarExpression.ConvertChecked embargo, es posible que desee en lugar, de modo que el desbordamiento numérico del rango del tipo de enumeración dé como resultado un OverflowException.
Drew Noakes
Su kilometraje puede variar, ejecuté el código en try.dot.net (blazor) y allí el EnumConverter <T> es mucho más lento que las alternativas. Lanzar al objeto primero fue aproximadamente 6 veces más lento que un lanzamiento directo, pero aún mucho mejor que las otras opciones.
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
fuente