2009-06-17 55 views
28

Tengo una enumeración que contiene lo siguiente (por ejemplo):C# Obtener Enum valores

  • ReinoUnido,
  • EstadosUnidos,
  • Francia,
  • Portugal

En mi código Yo uso Country.UnitedKingdom pero quiero que el valor sea UK si lo asigno a una cadena por ejemplo.

¿Esto es posible?

+0

posible duplicado de [C# Cadena enumeraciones] (http://stackoverflow.com/questions/424366/c-sharp-string-enums) – nawfal

Respuesta

52

No se puede asignar un valor enum a una cadena para comenzar. Tendría que llamar al ToString(), que convertiría Country.UnitedKingdom a "UnitedKingdom".

Dos opciones se sugieren:

  • Crear una Dictionary<Country, string>
  • Una sentencia switch
  • Decorar cada valor con un atributo, y la carga que con la reflexión

Comentarios acerca de cada una de ellas ...

Sam código PLE para Dictionary<Country,string>

using System; 
using System.Collections.Generic; 

enum Country 
{ 
    UnitedKingdom, 
    UnitedStates, 
    France, 
    Portugal 
} 

class Test 
{ 
    static readonly Dictionary<Country, string> CountryNames = 
     new Dictionary<Country, string> 
    { 
     { Country.UnitedKingdom, "UK" }, 
     { Country.UnitedStates, "US" }, 
    }; 

    static string ConvertCountry(Country country) 
    { 
     string name; 
     return (CountryNames.TryGetValue(country, out name)) 
      ? name : country.ToString(); 
    } 

    static void Main() 
    { 
     Console.WriteLine(ConvertCountry(Country.UnitedKingdom)); 
     Console.WriteLine(ConvertCountry(Country.UnitedStates)); 
     Console.WriteLine(ConvertCountry(Country.France)); 
    } 
} 

Es posible que desee poner la lógica de ConvertCountry en un método de extensión. Por ejemplo:

// Put this in a non-nested static class 
public static string ToBriefName(this Country country) 
{ 
    string name; 
    return (CountryNames.TryGetValue(country, out name)) 
     ? name : country.ToString(); 
} 

entonces se podría escribir:

string x = Country.UnitedKingdom.ToBriefName(); 

Como se ha mencionado en los comentarios, el diccionario comparador predeterminado implicará el boxeo, que no es lo ideal. Por una sola vez, viviría con eso hasta que descubriera que era un cuello de botella. Si estuviera haciendo esto para múltiples enumeraciones, escribiría una clase reutilizable.

sentencia switch

Estoy de acuerdo con yshuditelu's answer lo que sugiere el uso de una declaración switch de relativamente pocos casos. Sin embargo, ya que cada caso va a ser una sola instrucción, yo personalmente cambiar mi estilo de codificación para esta situación, para mantener el código compacto pero legible:

public static string ToBriefName(this Country country) 
{ 
    switch (country) 
    { 
     case Country.UnitedKingdom: return "UK"; 
     case Country.UnitedStates: return "US"; 
     default:      return country.ToString(); 
    } 
} 

Puede añadir más casos a esto sin que conseguir demasiado grande, y es fácil mirar a los ojos desde el valor enum hasta el valor de retorno.

DescriptionAttribute

El punto Rado made sobre el código de DescriptionAttribute ser reutilizable es buena, pero en ese caso se lo recomiendo contra el uso de la reflexión cada vez que se necesita para obtener un valor. Probablemente escribiría una clase estática genérica para mantener una tabla de búsqueda (probablemente un Dictionary, posiblemente con un comparador personalizado como se menciona en los comentarios). Los métodos de extensión no pueden ser definidos en clases genéricas, por lo que probablemente iba a terminar con algo como:

public static class EnumExtensions 
{ 
    public static string ToDescription<T>(this T value) where T : struct 
    { 
     return DescriptionLookup<T>.GetDescription(value); 
    } 

    private static class DescriptionLookup<T> where T : struct 
    { 
     static readonly Dictionary<T, string> Descriptions; 

     static DescriptionLookup() 
     { 
      // Initialize Descriptions here, and probably check 
      // that T is an enum 
     } 

     internal static string GetDescription(T value) 
     { 
      string description; 
      return Descriptions.TryGetValue(value, out description) 
       ? description : value.ToString(); 
     } 
    } 
} 
+0

Ejem, siempre podría cambiar los nombres de los valores de enumeración a su cadena representantes, si no te importa romper algunas reglas de estilo! –

+0

Lamentablemente, el diccionario funcionaría horriblemente: http://www.codeproject.com/KB/cs/EnumComparer.aspx –

+0

@hmemcpy hmm, siempre pensé que se realizaría básicamente como un Dictionary , dado que una enumeración es efectivamente una conjunto de int definidos como nombres, de ahí que pueda hacer enum i = {j = 0, b = 7} o lo que sea –

6

pseudo código:

enum MyCountryEnum 
{ 
    UnitedKingdom = 0, 
    UnitedStates = 1, 
    France = 2, 
    Portugal = 3, 
} 

string[] shortCodes = new string[] {"UK", "US", "FR", "PO"}; 


MyCountryEnum enumValue = MyCountryEnum.UnitedKingdom; 
string code = shortCodes[enumValue]; 
+7

El mantenimiento general es preocupante. Mantener sincronizadas dos estructuras de datos no conectadas es siempre una bandera roja. –

15

Se puede crear un método de extensión public static string ToShortString(this Country country). En el método, podría usar un diccionario estático como lo sugiere Jon, o simplemente podría hacer un caso de cambio.

Ejemplo:

public static class CountryExtensions 
{ 
    public static string ToShortString(this Country target) 
    { 
     switch (target) { 
      case Country.UnitedKingdom: 
       return "UK"; 
      case Country.UnitedStates: 
       return "US"; 
      case Country.France: 
       return "FR"; 
      case Country.Portugal: 
       return "PT"; 
      default: 
       return "None"; 
     } 
    } 
} 
+1

Con solo 4 valores, yo voto por la declaración de cambio. Refactorizar al diccionario si y cuando la instrucción switch se vuelve más engorrosa que la implementación del diccionario. – tvanfosson

+0

Espero que no te importe que agregué un ejemplo. – tvanfosson

+0

Sí, estoy de acuerdo con solo 4 conmutadores sería la elección correcta. Y gracias por agregar el código, aún no había llegado a eso. –

2

sólo tiene que utilizar el DescriptionAttribute

No hay necesidad de crear un diccionario si sólo se necesita para obtener una representación de cadena para sus valores de enumeración. Ver esto example

[EDITAR] Ah ... se olvidó de mencionar que es más reutilizable que los diccionarios, ya que solo necesita una clase util común para ayudar a obtener la descripción y luego todo lo que necesita hacer es agregar la DescripciónAtribuir la próxima vez que agregue un valor enum o cree una nueva enumeración con los mismos requisitos. En la solución de diccionario/interruptor, es más difícil de mantener y se vuelve complicado una vez que tienes muchos tipos de enumeración.

+0

De hecho, esa fue la otra alternativa que mencioné. Sin embargo, es más código y usa reflexión (que generalmente trato de evitar a menos que realmente lo necesite). Por supuesto, no hay nada mágico sobre DescriptionAttribute: podría crear su propio atributo y usarlo en su lugar. –

0
var codes = new Dictionary<Country, string>() 
     { { Country.UnitedKingdom, "UK" }, 
     { Country.UnitedStates, "US" }, 
     { Country.France, "FR" } }; 
Console.WriteLine(codes[Country.UnitedStates]); 
22

Prefiero usar el DescriptionAttribute en mis enums. Luego, puede usar el siguiente código para tomar esa descripción de la enumeración.

enum MyCountryEnum 
{  
    [Description("UK")] 
    UnitedKingdom = 0,  

    [Description("US")] 
    UnitedStates = 1,  

    [Description("FR")] 
    France = 2,  

    [Description("PO")] 
    Portugal = 3 
} 

public static string GetDescription(this Enum value) 
{ 
    var type = value.GetType(); 

    var fi = type.GetField(value.ToString()); 

    var descriptions = fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[]; 

    return descriptions.Length > 0 ? descriptions[0].Description : value.ToString(); 
} 

public static SortedDictionary<string, T> GetBoundEnum<T>() where T : struct, IConvertible 
{ 
    // validate. 
    if (!typeof(T).IsEnum) 
    { 
     throw new ArgumentException("T must be an Enum type."); 
    } 

    var results = new SortedDictionary<string, T>(); 

    FieldInfo[] fieldInfos = typeof(T).GetFields(); 

    foreach (var fi in fieldInfos) 
    { 

     var value = (T)fi.GetValue(fi); 
     var description = GetDescription((Enum)fi.GetValue(fi)); 

     if (!results.ContainsKey(description)) 
     { 
      results.Add(description, value); 
     } 
    } 
    return results; 
} 

Y después de conseguir mi lista de enumeración límite, es simplemente una llamada a

GetBoundEnum<MyCountryEnum>() 

Para obtener la descripción de una sola enumeración, usted sólo tiene que utilizar el método de extensión como esto

string whatever = MyCountryEnum.UnitedKingdom.GetDescription(); 
+1

Sigo obteniendo 'System.ArgumentException: campo 'value__' definido en el tipo 'MyClass.EnumHelperTest + MyCountryEnum' no es un campo en el objeto de destino que es de tipo 'System.Reflection.RtFieldInfo'. La investigación inicial sugiere que no' me gusta que enum no es una instancia? –

+0

@AlexAngas He corregido el error y proporcioné algunas otras mejoras menores a la respuesta de Scott aquí http://stackoverflow.com/a/35053745/625919 – DharmaTurtle

+1

@AlexAngas La solución a su problema se discute [aquí] (http: // www) .distribucon.com/blog/GettingMembersOfAnEnumViaReflection.aspx). En resumen, debe agregar algunos indicadores de enlace a la llamada 'GetFields' para excluir el campo de respaldo privado no estático con el nombre especial' value__' (consulte la línea 416 de [enumbuilder.cs en .Net 4.6.1] (http://referencesource.microsoft.com/#mscorlib/system/reflection/emit/enumbuilder.cs,25620f7fb1432241)). Por ejemplo, esta línea funciona: 'FieldInfo [] fieldInfos = typeof (T) .GetFields (BindingFlags.Public | BindingFlags.Static);' –

1

Cada vez que veo una enumeración siento que el código debe ser refactorizado. ¿Por qué no hacer una clase de país y agregar métodos para hacer algunos de los obstáculos que está tratando de evitar? Asignar valores a una enumeración es un olor a código aún mayor.

¿Por qué los votos a favor? Creo que es ampliamente aceptado que usar un enfoque polimórfico es mejor que usar una enumeración. No hay ninguna razón para usar una enumeración cuando puede usar el diseño de ValueObject en su lugar.

Aquí es un buen blog sobre el tema: http://devpinoy.org/blogs/cruizer/archive/2007/09/12/enums-are-evil.aspx

+0

Estoy de acuerdo que en casi todos los casos, cuando alguien está usando una enumeración, que probablemente debería hacer una doble toma en su código para ver si un Objeto podría ser una mejor opción. – Dan

+0

Esta es la forma más limpia de hacer las cosas. Me sorprende que no hayas recibido más votos favorables. – Kramii

0

La siguiente solución funciona (compila y se ejecuta). veo dos problemas:

  1. Usted tendría que asegurarse de que las enumeraciones son sincronizados. (Una prueba automatizada puede hacer eso por usted)

  2. Usted se basaría en el hecho de que las enumeraciones no son seguras en .NET.

    enum Country 
    { 
        UnitedKingdom = 0, 
        UnitedStates = 1, 
        France = 2, 
        Portugal = 3 
    } 
    
    enum CountryCode 
    { 
        UK = 0, 
        US = 1, 
        FR = 2, 
        PT = 3 
    } 
    
    void Main() 
    { 
        string countryCode = ((CountryCode)Country.UnitedKingdom).ToString(); 
        Console.WriteLine(countryCode); 
        countryCode = ((CountryCode)Country.Portugal).ToString(); 
        Console.WriteLine(countryCode); 
    } 
    
+1

Si bien esto funcionaría. Es probable que desee ser explícito en los valores en lugar de dejar que el compilador los agregue por usted. –

+0

Buen punto Matthew. He actualizado mi respuesta. – Klinger

3

Otra posibilidad que no se ha mencionado es algo como esto:

public class Country 
{ 
    public static readonly Country UnitedKingdom = new Country("UK"); 
    public static readonly Country UnitedStates = new Country("US"); 
    public static readonly Country France = new Country("FR"); 
    public static readonly Country Protugal = new Country("PT"); 

    private Country(string shortName) 
    { 
     ShortName = shortName; 
    } 

    public string ShortName { get; private set; } 
} 

Desde este punto se puede añadir más propiedades, pero cuidado con la cantidad que se agrega a la clase y cuántos miembros estáticos agregas, ya que la acumulación de memoria que agrega podría hacer que no valga la pena.

No creo que haya muchos casos en los que esta estrategia sea la mejor opción, pero es una opción que debe tenerse en cuenta al intentar agregar propiedades o atributos a algo que desee tratar como esencialmente una enumeración .

+1

Si le preocupa el uso de la memoria de sus objetos estáticos, podría convertirlos en propiedades estáticas de solo lectura y usar referencias débiles. http://msdn.microsoft.com/en-us/library/ms404247.aspx –

+0

@Matthew Whited: ¿me puede dar un ejemplo de cómo usar Weak References en este caso? – rohancragg

3

Tuve que dejar mi trabajo en este proyecto por un tiempo, y al volver a él, tuve un momento de inspiración.

En lugar de una enumeración, he creado una nueva clase de esta manera:

public class Country 
{ 
    public const string UnitedKingdom = "UK"; 
    public const string France = "F"; 
} 

De esta manera puedo usar Country.UnitedKingdom en mi código y el valor de "Reino Unido" se utilizará.

Acabo de publicar esta respuesta como una solución alternativa.

Neil

2

me trató de presentar una edición a la respuesta de Scott Ivey, pero fue rechazado, aquí hay otra respuesta. Mis ediciones relativamente menores:

1) He solucionado el error de Alex de System.ArgumentException: Field 'value__' defined on type 'MyClass.EnumHelperTest+MyCountryEnum' is not a field on the target object which is of type 'System.Reflection.RtFieldInfo'. Lo tengo desde here.

2) Agregó return para que pueda copiarlo/pegarlo y funcionará.

3) Cambió SortedDictionary a Dictionary porque SortedDictionary siempre ordena por la clave, en este caso la cadena Descripción. No hay ninguna razón para alfabetizar la Descripción. De hecho, clasificarlo destruye el orden original de la enumeración. Dictionary tampoco conserva el orden enum, pero al menos no implica orden como SortedDictionary.

enum MyCountryEnum 
{  
    [Description("UK")] 
    UnitedKingdom = 0,  

    [Description("US")] 
    UnitedStates = 1,  

    [Description("FR")] 
    France = 2,  

    [Description("PO")] 
    Portugal = 3 
} 

public static string GetDescription(this Enum value) 
{ 
    var type = value.GetType(); 

    var fi = type.GetField(value.ToString()); 

    var descriptions = fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[]; 

    return descriptions.Length > 0 ? descriptions[0].Description : value.ToString(); 
} 

public static Dictionary<string, T> GetBoundEnum<T>() where T : struct, IConvertible 
{ 
    // validate. 
    if (!typeof(T).IsEnum) 
    { 
     throw new ArgumentException("T must be an Enum type."); 
    } 

    var results = new Dictionary<string, T>(); 

    FieldInfo[] fieldInfos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static); 

    foreach (var fi in fieldInfos) 
    { 

     var value = (T)fi.GetValue(fi); 
     var description = GetDescription((Enum)fi.GetValue(fi)); 

     if (!results.ContainsKey(description)) 
     { 
      results.Add(description, value); 
     } 
    } 
    return results; 
}