Crear un método genérico que restrinja T a una enumeración


1190

Estoy construyendo una función para extender el Enum.Parseconcepto que

  • Permite analizar un valor predeterminado en caso de que no se encuentre un valor Enum
  • Es insensible a mayúsculas y minúsculas

Entonces escribí lo siguiente:

public static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
{
    if (string.IsNullOrEmpty(value)) return defaultValue;
    foreach (T item in Enum.GetValues(typeof(T)))
    {
        if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
    }
    return defaultValue;
}

Recibo una restricción de error no puede ser una clase especial System.Enum.

Es justo, pero hay una solución alternativa para permitir una enumeración genérica, o tendré que imitar la Parsefunción y pasar un tipo como un atributo, lo que obliga al requisito de boxeo feo a su código.

EDITAR Todas las sugerencias a continuación han sido muy apreciadas, gracias.

He decidido (he dejado el bucle para mantener la insensibilidad a mayúsculas y minúsculas; estoy usando esto al analizar XML)

public static class EnumUtils
{
    public static T ParseEnum<T>(string value, T defaultValue) where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type");
        if (string.IsNullOrEmpty(value)) return defaultValue;

        foreach (T item in Enum.GetValues(typeof(T)))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

EDITAR: (16 de febrero de 2015) Julien Lebosquain ha publicado recientemente una solución genérica segura de tipo forzada por el compilador en MSIL o F # a continuación, que bien vale la pena ver, y un voto positivo. Eliminaré esta edición si la solución aparece más arriba en la página.


10
Tal vez debería usar ToUpperInvariant () en lugar de ToLower () ...
Max Galkin

31
@Shimmy: Tan pronto como pase un tipo de valor al método de extensión, estará trabajando en una copia del mismo, por lo que no puede cambiar su estado.
Garo Yeriazarian

44
Sé que es un subproceso antiguo, no sé si cambiaron las cosas, pero los métodos de extensión funcionan bien para los tipos de valor, seguro que no siempre tienen tanto sentido, pero he usado "segundos de TimeSpan públicos estáticos (este int x) { return TimeSpan.FromSeconds (x);} "para habilitar la sintaxis de" Wait.For (5.Seconds ()) ... "
Jens

66
Ten en cuenta que esto no era parte de la pregunta, pero podrías mejorar tu lógica de bucles foreach usando String.Equals con StringComparison.InvariantCultureIgnoreCase
Firestrand el

Respuestas:


1006

Dado que EnumType implementa la IConvertibleinterfaz, una mejor implementación debería ser algo como esto:

public T GetEnumFromString<T>(string value) where T : struct, IConvertible
{
   if (!typeof(T).IsEnum) 
   {
      throw new ArgumentException("T must be an enumerated type");
   }

   //...
}

Esto seguirá permitiendo la implementación de tipos de valor IConvertible. Sin embargo, las posibilidades son raras.


2
Los genéricos están disponibles desde .NET 2.0. Por lo tanto, también están disponibles en vb 2005.
Vivek

46
Bueno, hazlo aún más limitado, si eliges seguir este camino ... usa "clase TestClass <T> donde T: struct, IComparable, IFormattable, IConvertible"
Ricardo Nolde

106
Otra sugerencia es definir el tipo genérico con el identificador TEnum. Por lo tanto: public TEnum GetEnumFromString <TEnum> (valor de cadena) donde TEnum: struct, IConvertible, IComparible, IFormattable {}
Lisa

11
No gana mucho al incluir las otras interfaces porque casi todos los tipos de valores incorporados implementan todas esas interfaces. Esto es especialmente cierto para las restricciones en un método de extensión genérico, que es extremadamente útil para operar en enumeraciones, excepto por el hecho de que esos métodos de extensión son como un virus que infecta todos sus objetos. IConvertable al menos lo reduce un poco.
Russbishop

2
@SamIam: Cuando publicaste, este hilo tenía qué, 6 años y medio, y estabas en lo correcto, no se comprobó en tiempo de compilación ninguna de las respuestas. Luego, solo 3 días después, después de 6 años, obtuviste tu deseo: mira la publicación de Julien Lebosquain a continuación.
David I. McIntosh

663

¡Esta característica finalmente es compatible con C # 7.3!

El siguiente fragmento (de las muestras de dotnet ) demuestra cómo:

public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
    var result = new Dictionary<int, string>();
    var values = Enum.GetValues(typeof(T));

    foreach (int item in values)
        result.Add(item, Enum.GetName(typeof(T), item));
    return result;
}

Asegúrese de configurar su versión de idioma en su proyecto C # a la versión 7.3.


Respuesta original a continuación:

Llego tarde al juego, pero lo tomé como un desafío para ver cómo podría hacerse. No es posible en C # (o VB.NET, pero desplácese hacia abajo para F #), pero es posible en MSIL. Escribí esta pequeña cosa ...

// license: http://www.apache.org/licenses/LICENSE-2.0.html
.assembly MyThing{}
.class public abstract sealed MyThing.Thing
       extends [mscorlib]System.Object
{
  .method public static !!T  GetEnumFromString<valuetype .ctor ([mscorlib]System.Enum) T>(string strValue,
                                                                                          !!T defaultValue) cil managed
  {
    .maxstack  2
    .locals init ([0] !!T temp,
                  [1] !!T return_value,
                  [2] class [mscorlib]System.Collections.IEnumerator enumerator,
                  [3] class [mscorlib]System.IDisposable disposer)
    // if(string.IsNullOrEmpty(strValue)) return defaultValue;
    ldarg strValue
    call bool [mscorlib]System.String::IsNullOrEmpty(string)
    brfalse.s HASVALUE
    br RETURNDEF         // return default it empty

    // foreach (T item in Enum.GetValues(typeof(T)))
  HASVALUE:
    // Enum.GetValues.GetEnumerator()
    ldtoken !!T
    call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
    callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator() 
    stloc enumerator
    .try
    {
      CONDITION:
        ldloc enumerator
        callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
        brfalse.s LEAVE

      STATEMENTS:
        // T item = (T)Enumerator.Current
        ldloc enumerator
        callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
        unbox.any !!T
        stloc temp
        ldloca.s temp
        constrained. !!T

        // if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        callvirt instance string [mscorlib]System.Object::ToString()
        callvirt instance string [mscorlib]System.String::ToLower()
        ldarg strValue
        callvirt instance string [mscorlib]System.String::Trim()
        callvirt instance string [mscorlib]System.String::ToLower()
        callvirt instance bool [mscorlib]System.String::Equals(string)
        brfalse.s CONDITION
        ldloc temp
        stloc return_value
        leave.s RETURNVAL

      LEAVE:
        leave.s RETURNDEF
    }
    finally
    {
        // ArrayList's Enumerator may or may not inherit from IDisposable
        ldloc enumerator
        isinst [mscorlib]System.IDisposable
        stloc.s disposer
        ldloc.s disposer
        ldnull
        ceq
        brtrue.s LEAVEFINALLY
        ldloc.s disposer
        callvirt instance void [mscorlib]System.IDisposable::Dispose()
      LEAVEFINALLY:
        endfinally
    }

  RETURNDEF:
    ldarg defaultValue
    stloc return_value

  RETURNVAL:
    ldloc return_value
    ret
  }
} 

Lo que genera una función que se vería así, si fuera válido C #:

T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum

Luego con el siguiente código C #:

using MyThing;
// stuff...
private enum MyEnum { Yes, No, Okay }
static void Main(string[] args)
{
    Thing.GetEnumFromString("No", MyEnum.Yes); // returns MyEnum.No
    Thing.GetEnumFromString("Invalid", MyEnum.Okay);  // returns MyEnum.Okay
    Thing.GetEnumFromString("AnotherInvalid", 0); // compiler error, not an Enum
}

Desafortunadamente, esto significa tener esta parte de su código escrita en MSIL en lugar de C #, con el único beneficio adicional de que puede restringir este método System.Enum. También es una especie de fastidio, porque se compila en un ensamblaje separado. Sin embargo, no significa que tenga que implementarlo de esa manera.

Al eliminar la línea .assembly MyThing{}e invocar ilasm de la siguiente manera:

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

obtienes un netmodule en lugar de un ensamblaje.

Desafortunadamente, VS2010 (y anterior, obviamente) no admite agregar referencias de netmodule, lo que significa que tendría que dejarlo en 2 ensamblajes separados cuando esté depurando. La única forma en que puede agregarlos como parte de su ensamblado sería ejecutar csc.exe usted mismo utilizando el /addmodule:{files}argumento de la línea de comandos. No sería demasiado doloroso en un script de MSBuild. Por supuesto, si eres valiente o estúpido, puedes ejecutar csc tú mismo manualmente cada vez. Y ciertamente se vuelve más complicado ya que múltiples ensambles necesitan acceso a él.

Entonces, PUEDE hacerse en .Net. ¿Vale la pena el esfuerzo extra? Um, bueno, supongo que te dejaré decidir sobre eso.


Solución F # como alternativa

Crédito adicional: Resulta que enumes posible una restricción genérica en al menos otro lenguaje .NET además de MSIL: F #.

type MyThing =
    static member GetEnumFromString<'T when 'T :> Enum> str defaultValue: 'T =
        /// protect for null (only required in interop with C#)
        let str = if isNull str then String.Empty else str

        Enum.GetValues(typedefof<'T>)
        |> Seq.cast<_>
        |> Seq.tryFind(fun v -> String.Compare(v.ToString(), str.Trim(), true) = 0)
        |> function Some x -> x | None -> defaultValue

Este es más fácil de mantener ya que es un lenguaje bien conocido con soporte completo de Visual Studio IDE, pero aún necesita un proyecto separado en su solución para ello. Sin embargo, naturalmente produce una IL considerablemente diferente (el código es muy diferente) y se basa en la FSharp.Corebiblioteca, que, al igual que cualquier otra biblioteca externa, debe formar parte de su distribución.

Así es como puede usarlo (básicamente lo mismo que la solución MSIL), y para mostrar que falla correctamente en otras estructuras:

// works, result is inferred to have type StringComparison
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", StringComparison.Ordinal);
// type restriction is recognized by C#, this fails at compile time
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", 42);

67
Sí, muy duro Tengo el máximo respeto por alguien que puede codificar en IL y sé cómo se admiten las características en el nivel de idioma superior, un nivel que muchos de nosotros todavía consideramos de bajo nivel en aplicaciones, reglas comerciales, interfaces de usuario, bibliotecas de componentes, etc. .
TonyG

13
Lo que realmente me gustaría saber es por qué el equipo de C # aún no ha comenzado a permitir esto, ya que MSIL ya lo admite.
MgSam

25
@MgSam - De Eric Lippert :There's no particularly unusual reason why not; we have lots of other things to do, limited budgets, and this one has never made it past the "wouldn't this be nice?" discussion in the language design team.
Christopher Currens

55
@LordofScripts: Creo que la razón es que, dado que una clase que restringe a Ta System.Enumno podría hacer todas las cosas con las Tque la gente podría esperar, los autores de C # pensaron que también podrían prohibirlo por completo. Considero que la decisión es desafortunada, ya que C # simplemente había ignorado cualquier manejo especial de System.Enumrestricciones, habría sido posible escribir un HasAnyFlags<T>(this T it, T other)método de extensión que fuera de órdenes de magnitud más rápido Enum.HasFlag(Enum)y que verificara sus argumentos.
supercat

99
No creo que haya tenido un proyecto en el que no termine aquí. C # 6 es 110% de azúcar sintáctica y ESTO no entró? Corta la basura.
Michael Blackburn

214

C # ≥ 7.3

A partir de C # 7.3 (disponible con Visual Studio 2017 ≥ v15.7), este código ahora es completamente válido:

public static TEnum Parse<TEnum>(string value)
    where TEnum : struct, Enum
{
 ...
}

C # ≤ 7.2

Puede tener una restricción de enumeración forzada del compilador real al abusar de la herencia de restricciones. El siguiente código especifica restricciones a classy structa al mismo tiempo:

public abstract class EnumClassUtils<TClass>
where TClass : class
{

    public static TEnum Parse<TEnum>(string value)
    where TEnum : struct, TClass
    {
        return (TEnum) Enum.Parse(typeof(TEnum), value);
    }

}

public class EnumUtils : EnumClassUtils<Enum>
{
}

Uso:

EnumUtils.Parse<SomeEnum>("value");

Nota: esto se especifica específicamente en la especificación del lenguaje C # 5.0:

Si el parámetro de tipo S depende del parámetro de tipo T, entonces: [...] es válido que S tenga la restricción de tipo de valor y que T tenga la restricción de tipo de referencia. Efectivamente, esto limita T a los tipos System.Object, System.ValueType, System.Enum y cualquier tipo de interfaz.


77
@ DavidI.McIntosh EnumClassUtils<System.Enum>es suficiente para restringir T a cualquier System.Enumtipo derivado. structen la Parsecontinuación restringe aún más a un verdadero tipo de enumeración. Necesita restringir a Enumen algún momento. Para hacerlo, tu clase tiene que estar anidada. Ver gist.github.com/MrJul/7da12f5f2d6c69f03d79
Julien Lebosquain el

77
Para ser claros, mi comentario "no agradable" no fue un comentario sobre su solución, es realmente un truco hermoso. Simplemente "no es agradable" que la EM nos obligue a usar un truco tan complicado.
David I. McIntosh

2
¿Hay alguna manera de hacer que esto también se pueda usar para los métodos de extensión?
Mord Zuber

3
¿Qué where TClass : classgana aquí la restricción?
tsemer

2
@Trinkyoenum DefinitelyNotAnInt : byte { Realize, That, I, Am, Not, An, Int } enum AlsoNotAnInt : long { Well, Bummer }
M.Stramm

30

Editar

La pregunta ahora ha sido respondida magníficamente por Julien Lebosquain . También me gustaría extender su respuesta con ignoreCase, defaultValuey argumentos opcionales, mientras agrega TryParsey ParseOrDefault.

public abstract class ConstrainedEnumParser<TClass> where TClass : class
// value type constraint S ("TEnum") depends on reference type T ("TClass") [and on struct]
{
    // internal constructor, to prevent this class from being inherited outside this code
    internal ConstrainedEnumParser() {}
    // Parse using pragmatic/adhoc hard cast:
    //  - struct + class = enum
    //  - 'guaranteed' call from derived <System.Enum>-constrained type EnumUtils
    public static TEnum Parse<TEnum>(string value, bool ignoreCase = false) where TEnum : struct, TClass
    {
        return (TEnum)Enum.Parse(typeof(TEnum), value, ignoreCase);
    }
    public static bool TryParse<TEnum>(string value, out TEnum result, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        var didParse = Enum.TryParse(value, ignoreCase, out result);
        if (didParse == false)
        {
            result = defaultValue;
        }
        return didParse;
    }
    public static TEnum ParseOrDefault<TEnum>(string value, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum result;
        if (Enum.TryParse(value, ignoreCase, out result)) { return result; }
        return defaultValue;
    }
}

public class EnumUtils: ConstrainedEnumParser<System.Enum>
// reference type constraint to any <System.Enum>
{
    // call to parse will then contain constraint to specific <System.Enum>-class
}

Ejemplos de uso:

WeekDay parsedDayOrArgumentException = EnumUtils.Parse<WeekDay>("monday", ignoreCase:true);
WeekDay parsedDayOrDefault;
bool didParse = EnumUtils.TryParse<WeekDay>("clubs", out parsedDayOrDefault, ignoreCase:true);
parsedDayOrDefault = EnumUtils.ParseOrDefault<WeekDay>("friday", ignoreCase:true, defaultValue:WeekDay.Sunday);

Antiguo

Mis viejas mejoras en la respuesta de Vivek mediante el uso de los comentarios y 'nuevos' desarrollos:

  • utilizar TEnumpara mayor claridad para los usuarios
  • agregue más restricciones de interfaz para la verificación adicional de restricciones
  • deje TryParsemanejar ignoreCasecon el parámetro existente (introducido en VS2010 / .Net 4)
  • opcionalmente use el defaultvalor genérico (introducido en VS2005 / .Net 2)
  • use argumentos opcionales (introducidos en VS2010 / .Net 4) con valores predeterminados, para defaultValueyignoreCase

Resultando en:

public static class EnumUtils
{
    public static TEnum ParseEnum<TEnum>(this string value,
                                         bool ignoreCase = true,
                                         TEnum defaultValue = default(TEnum))
        where TEnum : struct,  IComparable, IFormattable, IConvertible
    {
        if ( ! typeof(TEnum).IsEnum) { throw new ArgumentException("TEnum must be an enumerated type"); }
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum lResult;
        if (Enum.TryParse(value, ignoreCase, out lResult)) { return lResult; }
        return defaultValue;
    }
}

18

Puede definir un constructor estático para la clase que verificará que el tipo T sea una enumeración y arroje una excepción si no lo es. Este es el método mencionado por Jeffery Richter en su libro CLR a través de C #.

internal sealed class GenericTypeThatRequiresAnEnum<T> {
    static GenericTypeThatRequiresAnEnum() {
        if (!typeof(T).IsEnum) {
        throw new ArgumentException("T must be an enumerated type");
        }
    }
}

Luego, en el método de análisis, puede usar Enum.Parse (typeof (T), input, true) para convertir de cadena a enum. El último parámetro verdadero es para ignorar el caso de la entrada.


1
Esta es una buena opción para las clases genéricas, pero, por supuesto, no ayuda con los métodos genéricos.
McGarnagle

Además, esto tampoco se aplica en tiempo de compilación, solo sabría que proporcionó un non Enum Tcuando se ejecutó el constructor. Aunque esto es mucho mejor que esperar un constructor de instancias.
jrh

15

También se debe tener en cuenta que, dado que el lanzamiento de C # 7.3 con restricciones Enum se admite de forma inmediata sin tener que realizar comprobaciones adicionales y demás.

Entonces, en adelante, y dado que ha cambiado la versión de idioma de su proyecto a C # 7.3, el siguiente código funcionará perfectamente bien:

    private static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
    {
        // Your code goes here...
    }

En caso de que no sepa cómo cambiar la versión del idioma a C # 7.3, vea la siguiente captura de pantalla: ingrese la descripción de la imagen aquí

EDITAR 1 - Versión de Visual Studio requerida y considerando ReSharper

Para que Visual Studio reconozca la nueva sintaxis, necesita al menos la versión 15.7. Puede encontrar eso también mencionado en las notas de la versión de Microsoft, consulte las Notas de la versión de Visual Studio 2017 15.7 . Gracias @MohamedElshawaf por señalar esta pregunta válida.

También tenga en cuenta que en mi caso ReSharper 2018.1 al momento de escribir este EDIT todavía no es compatible con C # 7.3. Al tener ReSharper activado, resalta la restricción Enum como un error que me dice No se puede usar 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object' como restricción de parámetro de tipo . ReSharper sugiere como una solución rápida para eliminar la restricción 'Enum' del parámetro de tipo T del método

Sin embargo, si desactiva ReSharper temporalmente en Herramientas -> Opciones -> ReSharper Ultimate -> General , verá que la sintaxis está perfectamente bien dado que usa VS 15.7 o superior y C # 7.3 o superior.


1
¿Qué versión VS estás usando?
mshwf

1
@MohamedElshawaf Creo que es la versión 15.7 que contiene soporte para C # 7.3
Patrick Roberts

1
Creo que es mejor escribir where T : struct, Enum, para evitar pasarse a System.Enumsí mismo como parámetro de tipo.
Mariusz Pawelski

Me gusta @MariuszPawelski que escribo struct, Enum. Mi justificación se explica en la respuesta y los comentarios aquí .
Stephen Kennedy

La información de ReSharper realmente me ayudó. Tenga en cuenta que la última versión de vista previa admite esta función.
DalSoft

11

Modifiqué la muestra por dimarzionista. Esta versión solo funcionará con Enums y no permitirá que las estructuras pasen.

public static T ParseEnum<T>(string enumString)
    where T : struct // enum 
    {
    if (String.IsNullOrEmpty(enumString) || !typeof(T).IsEnum)
       throw new Exception("Type given must be an Enum");
    try
    {

       return (T)Enum.Parse(typeof(T), enumString, true);
    }
    catch (Exception ex)
    {
       return default(T);
    }
}

13
No devolvería el valor predeterminado en caso de falla; Dejaría que se propagara la excepción (tal como lo hace con Enum.Parse). En su lugar, use TryParse devolviendo un bool y devuelva el resultado usando un parámetro out.
Mark Simpson

1
OP quiere que no distinga entre mayúsculas y minúsculas, esto no lo es.
Konrad Morawski

9

Traté de mejorar un poco el código:

public T LoadEnum<T>(string value, T defaultValue = default(T)) where T : struct, IComparable, IFormattable, IConvertible
{
    if (Enum.IsDefined(typeof(T), value))
    {
        return (T)Enum.Parse(typeof(T), value, true);
    }
    return defaultValue;
}

1
Esto es mejor que la respuesta aceptada porque le permite llamar defaultValue.ToString("D", System.Globalization.NumberFormatInfo.CurrentInfo)a pesar de que no sabe qué tipo de enumeración es, solo que el objeto es una enumeración.
styfle

1
Sin IsDefinedembargo, la verificación previa con arruinará la insensibilidad de mayúsculas y minúsculas. A diferencia Parse, IsDefinedno tiene ignoreCaseargumento, y MSDN dice que solo coincide con el caso exacto .
Nyerguds

5

Tengo requisitos específicos donde requería usar enum con texto asociado con el valor de enum. Por ejemplo, cuando uso enum para especificar el tipo de error, es necesario que describa los detalles del error.

public static class XmlEnumExtension
{
    public static string ReadXmlEnumAttribute(this Enum value)
    {
        if (value == null) throw new ArgumentNullException("value");
        var attribs = (XmlEnumAttribute[]) value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof (XmlEnumAttribute), true);
        return attribs.Length > 0 ? attribs[0].Name : value.ToString();
    }

    public static T ParseXmlEnumAttribute<T>(this string str)
    {
        foreach (T item in Enum.GetValues(typeof(T)))
        {
            var attribs = (XmlEnumAttribute[])item.GetType().GetField(item.ToString()).GetCustomAttributes(typeof(XmlEnumAttribute), true);
            if(attribs.Length > 0 && attribs[0].Name.Equals(str)) return item;
        }
        return (T)Enum.Parse(typeof(T), str, true);
    }
}

public enum MyEnum
{
    [XmlEnum("First Value")]
    One,
    [XmlEnum("Second Value")]
    Two,
    Three
}

 static void Main()
 {
    // Parsing from XmlEnum attribute
    var str = "Second Value";
    var me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    // Parsing without XmlEnum
    str = "Three";
    me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    me = MyEnum.One;
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
}

4

Espero que esto sea útil:

public static TValue ParseEnum<TValue>(string value, TValue defaultValue)
                  where TValue : struct // enum 
{
      try
      {
            if (String.IsNullOrEmpty(value))
                  return defaultValue;
            return (TValue)Enum.Parse(typeof (TValue), value);
      }
      catch(Exception ex)
      {
            return defaultValue;
      }
}

1
Si necesita insensibilidad a mayúsculas y minúsculas, simplemente reemplace return (TValue)Enum.Parse(typeof (TValue), value);porreturn (TValue)Enum.Parse(typeof (TValue), value, true);
Paulo Santos

3

Curiosamente, aparentemente esto es posible en otros idiomas (Managed C ++, IL directamente).

Citar:

... Ambas restricciones en realidad producen IL válida y también puede ser consumida por C # si está escrita en otro idioma (puede declarar esas restricciones en C ++ administrado o en IL).

Quién sabe


2
Las extensiones administradas para C ++ no tienen NINGÚN soporte para genéricos, creo que te refieres a C ++ / CLI.
Ben Voigt

3

Esta es mi opinión al respecto. Combinado de las respuestas y MSDN

public static TEnum ParseToEnum<TEnum>(this string text) where TEnum : struct, IConvertible, IComparable, IFormattable
{
    if (string.IsNullOrEmpty(text) || !typeof(TEnum).IsEnum)
        throw new ArgumentException("TEnum must be an Enum type");

    try
    {
        var enumValue = (TEnum)Enum.Parse(typeof(TEnum), text.Trim(), true);
        return enumValue;
    }
    catch (Exception)
    {
        throw new ArgumentException(string.Format("{0} is not a member of the {1} enumeration.", text, typeof(TEnum).Name));
    }
}

Fuente MSDN


2
Esto realmente no tiene sentido. Si en TEnumrealidad es un tipo Enum pero textes una cadena vacía, ArgumentExceptionaparece el mensaje "TEnum debe ser un tipo Enum" aunque lo sea.
Nick

3

Las respuestas existentes son verdaderas a partir de C # <= 7.2. Sin embargo, hay una solicitud de función de lenguaje C # (vinculada a una solicitud de función corefx ) para permitir lo siguiente;

public class MyGeneric<TEnum> where TEnum : System.Enum
{ }

Al momento de escribir, la función está "en discusión" en las reuniones de desarrollo del lenguaje.

EDITAR

Según la información de nawfal , esto se está introduciendo en C # 7.3 .


1
Interesante discusión allí, gracias. Aún no hay
nada escrito

1
@johnc, muy cierto pero vale la pena mencionarlo y es una característica frecuente.
Probabilidades


1

Siempre me ha gustado esto (puedes modificarlo según corresponda):

public static IEnumerable<TEnum> GetEnumValues()
{
  Type enumType = typeof(TEnum);

  if(!enumType.IsEnum)
    throw new ArgumentException("Type argument must be Enum type");

  Array enumValues = Enum.GetValues(enumType);
  return enumValues.Cast<TEnum>();
}

1

Me encantó la solución de Christopher Currens usando IL, pero para aquellos que no quieren lidiar con el complicado negocio de incluir MSIL en su proceso de creación, escribí una función similar en C #.

Sin embargo, tenga en cuenta que no puede usar la restricción genérica where T : Enumporque Enum es de tipo especial. Por lo tanto, tengo que verificar si el tipo genérico dado es realmente enum.

Mi funcion es:

public static T GetEnumFromString<T>(string strValue, T defaultValue)
{
    // Check if it realy enum at runtime 
    if (!typeof(T).IsEnum)
        throw new ArgumentException("Method GetEnumFromString can be used with enums only");

    if (!string.IsNullOrEmpty(strValue))
    {
        IEnumerator enumerator = Enum.GetValues(typeof(T)).GetEnumerator();
        while (enumerator.MoveNext())
        {
            T temp = (T)enumerator.Current;
            if (temp.ToString().ToLower().Equals(strValue.Trim().ToLower()))
                return temp;
        }
    }

    return defaultValue;
}

1

Encapsulé la solución de Vivek en una clase de utilidad que puede reutilizar. Tenga en cuenta que aún debe definir restricciones de tipo "donde T: struct, IConvertible" en su tipo.

using System;

internal static class EnumEnforcer
{
    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your type parameter {0} is an enum.",
                typeParameterName,
                methodName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    /// <param name="inputParameterName">Name of the input parameter of this page.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName, string inputParameterName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your input parameter {2} is of correct type.",
                typeParameterName,
                methodName,
                inputParameterName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="exceptionMessage">Message to show in case T is not an enum.</param>
    public static void EnforceIsEnum<T>(string exceptionMessage)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException(exceptionMessage);
        }
    }
}

1

Creé un método de extensión to get integer value from enum eche un vistazo a la implementación del método

public static int ToInt<T>(this T soure) where T : IConvertible//enum
{
    if (typeof(T).IsEnum)
    {
        return (int) (IConvertible)soure;// the tricky part
    }
    //else
    //    throw new ArgumentException("T must be an enumerated type");
    return soure.ToInt32(CultureInfo.CurrentCulture);
}

este es el uso

MemberStatusEnum.Activated.ToInt()// using extension Method
(int) MemberStatusEnum.Activated //the ordinary way

Si bien probablemente funciona, casi no tiene relevancia para la pregunta.
quetzalcoatl

1

Como se indicó en otras respuestas antes; Si bien esto no se puede expresar en el código fuente, en realidad se puede hacer en el nivel IL. La respuesta de @Christopher Currens muestra cómo le va a la IL a eso.

Con Fody s de complementos ExtraConstraints.Fody hay una manera muy simple, con la acumulación de herramientas, para lograr esto. Simplemente agregue sus paquetes nuget ( Fody, ExtraConstraints.Fody) a su proyecto y agregue las restricciones de la siguiente manera (Extracto del archivo Léame de ExtraConstraints):

public void MethodWithEnumConstraint<[EnumConstraint] T>() {...}

public void MethodWithTypeEnumConstraint<[EnumConstraint(typeof(ConsoleColor))] T>() {...}

y Fody agregará la IL necesaria para que la restricción esté presente. También tenga en cuenta la característica adicional de restringir delegados:

public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
{...}

public void MethodWithTypeDelegateConstraint<[DelegateConstraint(typeof(Func<int>))] T> ()
{...}

Con respecto a Enums, es posible que también desee tomar nota de la muy interesante Enums.NET .


1

Esta es mi implementación. Básicamente, puede configurar cualquier atributo y funciona.

public static class EnumExtensions
    {
        public static string GetDescription(this Enum @enum)
        {
            Type type = @enum.GetType();
            FieldInfo fi = type.GetField(@enum.ToString());
            DescriptionAttribute[] attrs =
                fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
            if (attrs.Length > 0)
            {
                return attrs[0].Description;
            }
            return null;
        }
    }

0

Si está bien usar el lanzamiento directo después, supongo que puede usar la System.Enumclase base en su método, cuando sea necesario. Solo necesita reemplazar los parámetros de tipo cuidadosamente. Entonces la implementación del método sería como:

public static class EnumUtils
{
    public static Enum GetEnumFromString(string value, Enum defaultValue)
    {
        if (string.IsNullOrEmpty(value)) return defaultValue;
        foreach (Enum item in Enum.GetValues(defaultValue.GetType()))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

Entonces puedes usarlo como:

var parsedOutput = (YourEnum)EnumUtils.GetEnumFromString(someString, YourEnum.DefaultValue);

El uso de Enum.ToObject()produciría un resultado más flexible. Además de eso, usted podría hacer las comparaciones de cadenas sin mayúsculas y minúsculas que negar la necesidad de llamarToLower()
DiskJunky

-6

Solo para completar, la siguiente es una solución Java. Estoy seguro de que lo mismo podría hacerse en C # también. Evita tener que especificar el tipo en cualquier parte del código; en su lugar, debe especificarlo en las cadenas que está intentando analizar.

El problema es que no hay forma de saber con qué enumeración puede coincidir la Cadena, por lo que la respuesta es resolver ese problema.

En lugar de aceptar solo el valor de la cadena, acepte una cadena que tenga tanto la enumeración como el valor en la forma "enumeration.value". El código de trabajo está debajo: requiere Java 1.8 o posterior. Esto también haría que el XML sea más preciso, ya que vería algo como color = "Color.red" en lugar de simplemente color = "rojo".

Llamaría al método acceptEnumeratedValue () con una cadena que contiene el nombre del valor del punto nombre enum.

El método devuelve el valor enumerado formal.

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;


public class EnumFromString {

    enum NumberEnum {One, Two, Three};
    enum LetterEnum {A, B, C};


    Map<String, Function<String, ? extends Enum>> enumsByName = new HashMap<>();

    public static void main(String[] args) {
        EnumFromString efs = new EnumFromString();

        System.out.print("\nFirst string is NumberEnum.Two - enum is " + efs.acceptEnumeratedValue("NumberEnum.Two").name());
        System.out.print("\nSecond string is LetterEnum.B - enum is " + efs.acceptEnumeratedValue("LetterEnum.B").name());

    }

    public EnumFromString() {
        enumsByName.put("NumberEnum", s -> {return NumberEnum.valueOf(s);});
        enumsByName.put("LetterEnum", s -> {return LetterEnum.valueOf(s);});
    }

    public Enum acceptEnumeratedValue(String enumDotValue) {

        int pos = enumDotValue.indexOf(".");

        String enumName = enumDotValue.substring(0, pos);
        String value = enumDotValue.substring(pos + 1);

        Enum enumeratedValue = enumsByName.get(enumName).apply(value);

        return enumeratedValue;
    }


}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.