Representación de cadena de una enumeración

912

Tengo la siguiente enumeración:

public enum AuthenticationMethod
{
    FORMS = 1,
    WINDOWSAUTHENTICATION = 2,
    SINGLESIGNON = 3
}

Sin embargo, el problema es que necesito la palabra "FORMULARIOS" cuando solicito AuthenticationMethod.FORMS y no la identificación 1.

He encontrado la siguiente solución para este problema ( enlace ):

Primero necesito crear un atributo personalizado llamado "StringValue":

public class StringValue : System.Attribute
{
    private readonly string _value;

    public StringValue(string value)
    {
        _value = value;
    }

    public string Value
    {
        get { return _value; }
    }

}

Entonces puedo agregar este atributo a mi enumerador:

public enum AuthenticationMethod
{
    [StringValue("FORMS")]
    FORMS = 1,
    [StringValue("WINDOWS")]
    WINDOWSAUTHENTICATION = 2,
    [StringValue("SSO")]
    SINGLESIGNON = 3
}

Y, por supuesto, necesito algo para recuperar ese StringValue:

public static class StringEnum
{
    public static string GetStringValue(Enum value)
    {
        string output = null;
        Type type = value.GetType();

        //Check first in our cached results...

        //Look for our 'StringValueAttribute' 

        //in the field's custom attributes

        FieldInfo fi = type.GetField(value.ToString());
        StringValue[] attrs =
           fi.GetCustomAttributes(typeof(StringValue),
                                   false) as StringValue[];
        if (attrs.Length > 0)
        {
            output = attrs[0].Value;
        }

        return output;
    }
}

Bien, ahora tengo las herramientas para obtener un valor de cadena para un enumerador. Entonces puedo usarlo así:

string valueOfAuthenticationMethod = StringEnum.GetStringValue(AuthenticationMethod.FORMS);

Bien, ahora todo esto funciona como un encanto, pero me parece mucho trabajo. Me preguntaba si hay una mejor solución para esto.

También probé algo con un diccionario y propiedades estáticas, pero tampoco fue mejor.

usuario29964
fuente
8
¡Agradable! Puedo usar esto para traducir valores enum a cadenas localizadas.
Øyvind Skaar
55
Si bien puede encontrar esto largo aliento, en realidad es una forma bastante flexible de hacer otras cosas. Como uno de mis colegas ha señalado, esto podría ser utilizado en muchos casos para reemplazar Enum ayudantes que los códigos de base de datos de mapa para ENUM valores, etc ...
BenAlabaster
27
Clases de atributo de sufijo de recomendaciones de MSDN con sufijo "Atributo". Entonces "class StringValueAttribute";)
serhio
14
Estoy de acuerdo con @BenAlabaster, esto es realmente bastante flexible. Además, puede hacer de este un método de extensión simplemente agregando thisen frente de Enumsu método estático. Entonces puedes hacerlo AuthenticationMethod.Forms.GetStringValue();
Justin Pihony
55
Este enfoque utiliza la reflexión para leer los valores de los atributos y es muy lento si tiene que llamar a GetStringValue () muchas veces en mi experiencia. El patrón type-safe-enum es más rápido.
Rn222

Respuestas:

868

Prueba el patrón type-safe-enum .

public sealed class AuthenticationMethod {

    private readonly String name;
    private readonly int value;

    public static readonly AuthenticationMethod FORMS = new AuthenticationMethod (1, "FORMS");
    public static readonly AuthenticationMethod WINDOWSAUTHENTICATION = new AuthenticationMethod (2, "WINDOWS");
    public static readonly AuthenticationMethod SINGLESIGNON = new AuthenticationMethod (3, "SSN");        

    private AuthenticationMethod(int value, String name){
        this.name = name;
        this.value = value;
    }

    public override String ToString(){
        return name;
    }

}

La actualización de conversión de tipo explícito (o implícito) se puede hacer mediante

  • Agregar campo estático con mapeo

    private static readonly Dictionary<string, AuthenticationMethod> instance = new Dictionary<string,AuthenticationMethod>();
    • nb Para que la inicialización de los campos "enum member" no arroje una NullReferenceException al llamar al constructor de la instancia, asegúrese de colocar el campo Dictionary antes de los campos "enum member" en su clase. Esto se debe a que los inicializadores de campo estático se llaman en orden de declaración, y antes del constructor estático, creando la situación extraña y necesaria pero confusa de que se puede llamar al constructor de instancia antes de que todos los campos estáticos se hayan inicializado, y antes de que se llame al constructor estático.
  • llenar este mapeo en el constructor de instancias

    instance[name] = this;
  • y agregar operador de conversión de tipo definido por el usuario

    public static explicit operator AuthenticationMethod(string str)
    {
        AuthenticationMethod result;
        if (instance.TryGetValue(str, out result))
            return result;
        else
            throw new InvalidCastException();
    }
    
Jakub Šturc
fuente
17
Parece una enumeración pero no es una enumeración. Me imagino que esto causa algunos problemas interesantes si la gente comienza a intentar comparar AuthenticationMethods. Probablemente también necesite sobrecargar varios operadores de igualdad.
Ant
36
@Ant: no tengo que hacerlo. Como solo tenemos una instancia de cada AuthenticationMethod, la igualdad de referencia heredada de Object funciona bien.
Jakub Šturc
10
@tyriker: el compilador sí. El constructor es privado, por lo que no puede crear una nueva instancia. También los miembros estáticos no son accesibles a través de la instancia.
Jakub Šturc
21
@Jakub Muy interesante. Tuve que jugar con él para descubrir cómo usarlo y darme cuenta de sus beneficios. Es una clase pública, no estática, pero no se puede instanciar y solo puede acceder a sus miembros estáticos. Básicamente, se comporta como una enumeración. Pero la mejor parte ... los miembros estáticos se escriben de la clase y no una cadena genérica o int. Es un ... [espere] ... escriba safe enum! Gracias por ayudarme a entender.
tyriker
66
@kiran He publicado una versión ligeramente modificada de la respuesta de Jakub Šturc a continuación que permite que se use con declaraciones de cambio de caso, por lo que ahora no hay inconveniente en este enfoque :)
deadlydog
228

Utilizar el método

Enum.GetName(Type MyEnumType,  object enumvariable)  

como en (Suponga que Shipperes una enumeración definida)

Shipper x = Shipper.FederalExpress;
string s = Enum.GetName(typeof(Shipper), x);

También hay muchos otros métodos estáticos en la clase Enum que vale la pena investigar ...

Charles Bretana
fuente
55
Exactamente. Realicé un atributo personalizado para una descripción de cadena, pero eso es porque quiero una versión fácil de usar (con espacios y otros caracteres especiales) que pueda vincularse fácilmente a un ComboBox o similar.
lc.
55
Enum.GetName refleja los nombres de campo en la enumeración, igual que .ToString (). Si el rendimiento es un problema, puede ser un problema. Sin embargo, no me preocuparía a menos que esté convirtiendo un montón de enumeraciones.
Keith
8
Otra opción a considerar, si necesita una enumeración con funcionalidad adicional, es "rodar su propio" usando una estructura ... agrega propiedades estáticas de solo lectura para representar los valores de enumeración que se inicializan a los constructores que generan instancias individuales de la estructura ...
Charles Bretana
1
entonces se puede agregar lo que otros miembros de la estructura que desea, para poner en práctica lo que sea funcionalidad desea que este "enumeración" que tienen ...
Charles Bretana
2
El problema aquí es que GetName no es localizable. Eso no siempre es una preocupación, pero es algo a tener en cuenta.
Joel Coehoorn el
79

Puede hacer referencia al nombre en lugar del valor utilizando ToString ()

Console.WriteLine("Auth method: {0}", AuthenticationMethod.Forms.ToString());

La documentación está aquí:

http://msdn.microsoft.com/en-us/library/16c1xs4z.aspx

... y si nombra sus enumeraciones en Pascal Case (como lo hago yo, como ThisIsMyEnumValue = 1, etc.), podría usar una expresión regular muy simple para imprimir el formulario descriptivo:

static string ToFriendlyCase(this string EnumString)
{
    return Regex.Replace(EnumString, "(?!^)([A-Z])", " $1");
}

que se puede llamar fácilmente desde cualquier cadena:

Console.WriteLine("ConvertMyCrazyPascalCaseSentenceToFriendlyCase".ToFriendlyCase());

Salidas:

Convierta mi oración de caso Crazy Pascal en un caso amiga

Eso ahorra correr alrededor de las casas creando atributos personalizados y adjuntándolos a sus enumeraciones o usando tablas de búsqueda para unir un valor de enumeración con una cadena amigable y, lo mejor de todo, es autoadministrable y se puede usar en cualquier cadena Pascal Case que sea infinitamente Más reutilizable. Por supuesto, no le permite tener un nombre descriptivo diferente al de su enumeración que proporciona su solución.

Sin embargo, me gusta su solución original para escenarios más complejos. Podría llevar su solución un paso más allá y convertir su GetStringValue en un método de extensión de su enumeración y luego no necesitaría hacer referencia a él como StringEnum.GetStringValue ...

public static string GetStringValue(this AuthenticationMethod value)
{
  string output = null;
  Type type = value.GetType();
  FieldInfo fi = type.GetField(value.ToString());
  StringValue[] attrs = fi.GetCustomAttributes(typeof(StringValue), false) as StringValue[];
  if (attrs.Length > 0)
    output = attrs[0].Value;
  return output;
}

A continuación, puede acceder fácilmente desde su instancia de enumeración:

Console.WriteLine(AuthenticationMethod.SSO.GetStringValue());
BenAlabaster
fuente
2
Esto no ayuda si el "nombre descriptivo" necesita un espacio. Tales como "Autenticación de formularios"
Ray Booysen
44
Así que asegúrese de que la enumeración tenga un nombre con mayúsculas como FormsAuthentication e inserte un espacio antes de las mayúsculas que no estén al principio. No es ciencia de cohetes para insertar un espacio en una cadena ...
BenAlabaster
44
El espaciado automático de los nombres de Pascal Case se vuelve problemático si contienen abreviaturas que deberían ser mayúsculas, XML o GPS, por ejemplo.
Richard Ev
2
@ Richardic, no hay una expresión regular perfecta para esto, pero aquí hay una que debería funcionar un poco mejor con las abreviaturas. "(?!^)([^A-Z])([A-Z])", "$1 $2". Así se HereIsATESTconvierte Here Is ATEST.
sparebytes
No elegent haciendo estos pequeños "hacks", que son lo que son. Entiendo lo que dice el OP y estoy tratando de encontrar una solución similar, es decir, usar la elegancia de Enums pero poder acceder fácilmente al mensaje asociado. La única solución que se me ocurre es aplicar algún tipo de mapeo entre el nombre de la enumeración y el valor de una cadena, pero eso no soluciona el problema del mantenimiento de los datos de la cadena (sin embargo, resulta práctico para escenarios en los que necesita tener múltiples regiones, etc. )
Tahir Khalid el
72

Lamentablemente, la reflexión para obtener atributos en las enumeraciones es bastante lenta:

Vea esta pregunta: ¿ Alguien sabe una forma rápida de obtener atributos personalizados en un valor de enumeración?

El .ToString()es bastante lento en enumeraciones también.

Sin embargo, puede escribir métodos de extensión para enumeraciones:

public static string GetName( this MyEnum input ) {
    switch ( input ) {
        case MyEnum.WINDOWSAUTHENTICATION:
            return "Windows";
        //and so on
    }
}

Esto no es genial, pero será rápido y no requerirá la reflexión de los atributos o el nombre del campo.


Actualización C # 6

Si puede usar C # 6, entonces el nuevo nameofoperador funciona para las enumeraciones, por nameof(MyEnum.WINDOWSAUTHENTICATION)lo que se convertirá "WINDOWSAUTHENTICATION"en tiempo de compilación , por lo que es la forma más rápida de obtener nombres de enumeración.

Tenga en cuenta que esto convertirá la enumeración explícita en una constante en línea, por lo que no funciona para las enumeraciones que tiene en una variable. Entonces:

nameof(AuthenticationMethod.FORMS) == "FORMS"

Pero...

var myMethod = AuthenticationMethod.FORMS;
nameof(myMethod) == "myMethod"
Keith
fuente
24
Puede obtener los valores de los atributos una vez y ponerlos en un Diccionario <MyEnum, string> para mantener el aspecto declarativo.
Jon Skeet
1
Sí, eso es lo que terminamos haciendo en una aplicación con muchas enumeraciones cuando descubrimos que el reflejo era el cuello de botella.
Keith
Gracias Jon y Keith, terminé usando tu sugerencia de Diccionario. Funciona muy bien (¡y rápido!).
Helge Klein el
@ JonSkeet Sé que esto es viejo. Pero, ¿cómo se lograría esto?
user919426
2
@ user919426: ¿Lograr lo que quieres? ¿Ponerlos en un diccionario? Simplemente cree un diccionario, idealmente con un inicializador de colección ... no está claro lo que está pidiendo.
Jon Skeet
59

Yo uso un método de extensión:

public static class AttributesHelperExtension
    {
        public static string ToDescription(this Enum value)
        {
            var da = (DescriptionAttribute[])(value.GetType().GetField(value.ToString())).GetCustomAttributes(typeof(DescriptionAttribute), false);
            return da.Length > 0 ? da[0].Description : value.ToString();
        }
}

Ahora decora el enumcon:

public enum AuthenticationMethod
{
    [Description("FORMS")]
    FORMS = 1,
    [Description("WINDOWSAUTHENTICATION")]
    WINDOWSAUTHENTICATION = 2,
    [Description("SINGLESIGNON ")]
    SINGLESIGNON = 3
}

Cuando usted llama

AuthenticationMethod.FORMS.ToDescription()obtendrá "FORMS".

Mangesh Pimpalkar
fuente
1
Tuve que agregar using System.ComponentModel;Además, este método solo funciona si desea que el valor de la cadena sea el mismo que el nombre de Enum. OP quería un valor diferente.
elcool
2
¿No quieres decir cuando llamas AuthenticationMethod.FORMS.ToDescription()?
nicodemus13
41

Solo usa el ToString()método

public enum any{Tomato=0,Melon,Watermelon}

Para hacer referencia a la cadena Tomato, solo use

any.Tomato.ToString();
chepe
fuente
Guau. Eso fue fácil. Sé que el OP quería agregar descripciones de cadenas personalizadas, pero esto es lo que necesitaba. Debería haber sabido probar esto, en retrospectiva, pero seguí la ruta Enum.GetName.
Rafe
77
¿Por qué todos los demás complican demasiado esto?
Brent
18
@Brent Porque la mayoría de las veces tiene un .ToString()valor diferente al fácil de usar que necesita.
Novitchi S
2
@Brent - porque esto es diferente a la pregunta que se hace. La pregunta que se hace es cómo obtener esta cadena de una variable a la que se le ha asignado un valor enumerado. Eso es dinámico en tiempo de ejecución. Esto verifica la definición del tipo y se establece en tiempo de ejecución.
Hogan
1
@Hogan - ToString () también funciona en variables: any fruit = any.Tomato; string tomato = fruit.ToString();
LiborV
29

Solución muy simple a esto con .Net 4.0 y superior. No se necesita otro código.

public enum MyStatus
{
    Active = 1,
    Archived = 2
}

Para obtener la cadena sobre solo use:

MyStatus.Active.ToString("f");

o

MyStatus.Archived.ToString("f");`

El valor será "Activo" o "Archivado".

Para ver los diferentes formatos de cadena (la "f" de arriba) al llamar, Enum.ToStringvea esta página de cadenas de formato de enumeración

David C
fuente
28

Uso el atributo Descripción del espacio de nombres System.ComponentModel. Simplemente decore la enumeración y luego use este código para recuperarla:

public static string GetDescription<T>(this object enumerationValue)
            where T : struct
        {
            Type type = enumerationValue.GetType();
            if (!type.IsEnum)
            {
                throw new ArgumentException("EnumerationValue must be of Enum type", "enumerationValue");
            }

            //Tries to find a DescriptionAttribute for a potential friendly name
            //for the enum
            MemberInfo[] memberInfo = type.GetMember(enumerationValue.ToString());
            if (memberInfo != null && memberInfo.Length > 0)
            {
                object[] attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

                if (attrs != null && attrs.Length > 0)
                {
                    //Pull out the description value
                    return ((DescriptionAttribute)attrs[0]).Description;
                }
            }
            //If we have no description attribute, just return the ToString of the enum
            return enumerationValue.ToString();

        }

Como ejemplo:

public enum Cycle : int
{        
   [Description("Daily Cycle")]
   Daily = 1,
   Weekly,
   Monthly
}

Este código satisface muy bien las enumeraciones en las que no necesita un "nombre descriptivo" y devolverá solo el .ToString () de la enumeración.

Ray Booysen
fuente
27

Realmente me gusta la respuesta de Jakub Šturc, pero su inconveniente es que no se puede usar con una declaración de cambio de mayúsculas y minúsculas. Aquí hay una versión ligeramente modificada de su respuesta que se puede usar con una declaración de cambio:

public sealed class AuthenticationMethod
{
    #region This code never needs to change.
    private readonly string _name;
    public readonly Values Value;

    private AuthenticationMethod(Values value, String name){
        this._name = name;
        this.Value = value;
    }

    public override String ToString(){
        return _name;
    }
    #endregion

    public enum Values
    {
        Forms = 1,
        Windows = 2,
        SSN = 3
    }

    public static readonly AuthenticationMethod FORMS = new AuthenticationMethod (Values.Forms, "FORMS");
    public static readonly AuthenticationMethod WINDOWSAUTHENTICATION = new AuthenticationMethod (Values.Windows, "WINDOWS");
    public static readonly AuthenticationMethod SINGLESIGNON = new AuthenticationMethod (Values.SSN, "SSN");
}

Entonces obtendrá todos los beneficios de la respuesta de Jakub Šturc, además podemos usarlo con una declaración de cambio como esta:

var authenticationMethodVariable = AuthenticationMethod.FORMS;  // Set the "enum" value we want to use.
var methodName = authenticationMethodVariable.ToString();       // Get the user-friendly "name" of the "enum" value.

// Perform logic based on which "enum" value was chosen.
switch (authenticationMethodVariable.Value)
{
    case authenticationMethodVariable.Values.Forms: // Do something
        break;
    case authenticationMethodVariable.Values.Windows: // Do something
        break;
    case authenticationMethodVariable.Values.SSN: // Do something
        break;      
}
perro mortal
fuente
Una solución más corta sería eliminar las enumeraciones {} y, en su lugar, mantener un recuento estático de cuántas enumeraciones ha construido. Esto también brinda el beneficio de que no tiene que agregar una nueva instancia que haga a la lista de enumeraciones. por ejemplo, public static int nextAvailable { get; private set; }luego en el constructorthis.Value = nextAvailable++;
kjhf
Idea interesante @kjhf. Sin embargo, mi preocupación sería que si alguien reordena el código, entonces el valor asignado a los valores de enumeración también podría cambiar. Por ejemplo, esto podría provocar que se recupere el valor de enumeración incorrecto cuando el valor de enumeración se guarda en un archivo / base de datos, se cambia el orden de las líneas "nuevo Método de autenticación (...)" (por ejemplo, se elimina uno) y luego ejecutar la aplicación nuevamente y recuperar el valor de enumeración del archivo / base de datos; el valor de enumeración puede no coincidir con el Método de autenticación que se guardó originalmente.
deadlydog
Buen punto, aunque espero que en estos casos particulares las personas no se basen en el valor entero de la enumeración (o reordenando el código de enumeración). Este valor se usa exclusivamente como un interruptor y posiblemente una alternativa a .Equals () y. GetHashCode (). Si está preocupado, siempre puede poner un gran comentario con "NO REORDENAR": p
kjhf
¿No puede simplemente sobrecargar el =operador para permitir que el interruptor funcione? Hice esto en VB y ahora puedo usarlo en la select casedeclaración.
user1318499
@ user1318499 No, C # tiene reglas más estrictas en torno a la declaración de cambio que VB. No puede usar instancias de clase para la instrucción Case; solo puedes usar primitivas constantes.
deadlydog
13

Utilizo una combinación de varias de las sugerencias anteriores, combinadas con un poco de almacenamiento en caché. Ahora, tuve la idea de un código que encontré en algún lugar de la red, pero no puedo recordar dónde lo obtuve ni lo encontré. Entonces, si alguien encuentra algo similar, por favor comente con la atribución.

De todos modos, el uso implica los convertidores de tipo, por lo que si está vinculado a la interfaz de usuario, 'simplemente funciona'. Puede ampliar con el patrón de Jakub para una búsqueda rápida de código inicializando desde el convertidor de tipos en los métodos estáticos.

El uso base se vería así

[TypeConverter(typeof(CustomEnumTypeConverter<MyEnum>))]
public enum MyEnum
{
    // The custom type converter will use the description attribute
    [Description("A custom description")]
    ValueWithCustomDescription,

   // This will be exposed exactly.
   Exact
}

El código para el convertidor de tipo de enumeración personalizado es el siguiente:

public class CustomEnumTypeConverter<T> : EnumConverter
    where T : struct
{
    private static readonly Dictionary<T,string> s_toString = 
      new Dictionary<T, string>();

    private static readonly Dictionary<string, T> s_toValue = 
      new Dictionary<string, T>();

    private static bool s_isInitialized;

    static CustomEnumTypeConverter()
    {
        System.Diagnostics.Debug.Assert(typeof(T).IsEnum,
          "The custom enum class must be used with an enum type.");
    }

    public CustomEnumTypeConverter() : base(typeof(T))
    {
        if (!s_isInitialized)
        {
            Initialize();
            s_isInitialized = true;
        }
    }

    protected void Initialize()
    {
        foreach (T item in Enum.GetValues(typeof(T)))
        {
            string description = GetDescription(item);
            s_toString[item] = description;
            s_toValue[description] = item;
        }
    }

    private static string GetDescription(T optionValue)
    {
        var optionDescription = optionValue.ToString();
        var optionInfo = typeof(T).GetField(optionDescription);
        if (Attribute.IsDefined(optionInfo, typeof(DescriptionAttribute)))
        {
            var attribute = 
              (DescriptionAttribute)Attribute.
                 GetCustomAttribute(optionInfo, typeof(DescriptionAttribute));
            return attribute.Description;
        }
        return optionDescription;
    }

    public override object ConvertTo(ITypeDescriptorContext context, 
       System.Globalization.CultureInfo culture, 
       object value, Type destinationType)
    {
        var optionValue = (T)value;

        if (destinationType == typeof(string) && 
            s_toString.ContainsKey(optionValue))
        {
            return s_toString[optionValue];
        }

        return base.ConvertTo(context, culture, value, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, 
       System.Globalization.CultureInfo culture, object value)
    {
        var stringValue = value as string;

        if (!string.IsNullOrEmpty(stringValue) && s_toValue.ContainsKey(stringValue))
        {
            return s_toValue[stringValue];
        }

        return base.ConvertFrom(context, culture, value);
    }
}

}

Steve Mitcham
fuente
12

En su pregunta, nunca dijo que realmente necesita el valor numérico de la enumeración en ninguna parte.

Si no lo hace y solo necesita una enumeración de tipo cadena (que no es un tipo integral, por lo que no puede ser una base de enumeración), aquí hay una manera:

    static class AuthenticationMethod
    {
        public static readonly string
            FORMS = "Forms",
            WINDOWSAUTHENTICATION = "WindowsAuthentication";
    }

puede usar la misma sintaxis que enum para hacer referencia a ella

if (bla == AuthenticationMethod.FORMS)

Será un poco más lento que con los valores numéricos (comparando cadenas en lugar de números) pero en el lado positivo no está usando la reflexión (lenta) para acceder a la cadena.

ILIA BROUDNO
fuente
si usa "const" en lugar de "static readonly", puede usar los valores como etiquetas de caso en una declaración de cambio.
Ed N.
11

Cómo resolví esto como un método de extensión:

using System.ComponentModel;
public static string GetDescription(this Enum value)
{
    var descriptionAttribute = (DescriptionAttribute)value.GetType()
        .GetField(value.ToString())
        .GetCustomAttributes(false)
        .Where(a => a is DescriptionAttribute)
        .FirstOrDefault();

    return descriptionAttribute != null ? descriptionAttribute.Description : value.ToString();
}

Enum:

public enum OrderType
{
    None = 0,
    [Description("New Card")]
    NewCard = 1,
    [Description("Reload")]
    Refill = 2
}

Uso (donde o.OrderType es una propiedad con el mismo nombre que la enumeración):

o.OrderType.GetDescription()

Lo que me da una cadena de "Nueva tarjeta" o "Recargar" en lugar del valor de enumeración real NewCard and Refill.

Segundo
fuente
Para completar, debe incluir una copia de su clase DescriptionAttribute.
Bernie White
3
Bernie, DescriptionAttribute está en System.ComponentModel
agentnega
11

Actualización: Visitar esta página, 8 años después, después de no tocar C # durante mucho tiempo, parece que mi respuesta ya no es la mejor solución. Realmente me gusta la solución del convertidor vinculada con las funciones de atributo.

Si está leyendo esto, asegúrese de consultar también otras respuestas.
(pista: están por encima de este)


Como la mayoría de ustedes, me gustó mucho la respuesta seleccionada de Jakub Šturc , pero también odio copiar y pegar el código, y trato de hacerlo lo menos posible.

Así que decidí que quería una clase EnumBase de la que se heredara / incorporase la mayor parte de la funcionalidad, dejándome concentrarme en el contenido en lugar del comportamiento.

El principal problema con este enfoque se basa en el hecho de que, aunque los valores de Enum son instancias de tipo seguro, la interacción es con la implementación estática del tipo de clase Enum. Entonces, con un poco de ayuda de la magia genérica, creo que finalmente obtuve la mezcla correcta. Espero que alguien encuentre esto tan útil como yo.

Comenzaré con el ejemplo de Jakub, pero usando herencia y genéricos:

public sealed class AuthenticationMethod : EnumBase<AuthenticationMethod, int>
{
    public static readonly AuthenticationMethod FORMS =
        new AuthenticationMethod(1, "FORMS");
    public static readonly AuthenticationMethod WINDOWSAUTHENTICATION =
        new AuthenticationMethod(2, "WINDOWS");
    public static readonly AuthenticationMethod SINGLESIGNON =
        new AuthenticationMethod(3, "SSN");

    private AuthenticationMethod(int Value, String Name)
        : base( Value, Name ) { }
    public new static IEnumerable<AuthenticationMethod> All
    { get { return EnumBase<AuthenticationMethod, int>.All; } }
    public static explicit operator AuthenticationMethod(string str)
    { return Parse(str); }
}

Y aquí está la clase base:

using System;
using System.Collections.Generic;
using System.Linq; // for the .AsEnumerable() method call

// E is the derived type-safe-enum class
// - this allows all static members to be truly unique to the specific
//   derived class
public class EnumBase<E, T> where E: EnumBase<E, T>
{
    #region Instance code
    public T Value { get; private set; }
    public string Name { get; private set; }

    protected EnumBase(T EnumValue, string Name)
    {
        Value = EnumValue;
        this.Name = Name;
        mapping.Add(Name, this);
    }

    public override string ToString() { return Name; }
    #endregion

    #region Static tools
    static private readonly Dictionary<string, EnumBase<E, T>> mapping;
    static EnumBase() { mapping = new Dictionary<string, EnumBase<E, T>>(); }
    protected static E Parse(string name)
    {
        EnumBase<E, T> result;
        if (mapping.TryGetValue(name, out result))
        {
            return (E)result;
        }

        throw new InvalidCastException();
    }
    // This is protected to force the child class to expose it's own static
    // method.
    // By recreating this static method at the derived class, static
    // initialization will be explicit, promising the mapping dictionary
    // will never be empty when this method is called.
    protected static IEnumerable<E> All
    { get { return mapping.Values.AsEnumerable().Cast<E>(); } }
    #endregion
}
Cerrajero
fuente
Es posible que pueda llamar al constructor estático derivado desde el constructor estático base. Todavía lo estoy investigando, pero hasta ahora no he encontrado ningún problema: stackoverflow.com/questions/55290034/…
Cory-G
10

Estoy de acuerdo con Keith, pero no puedo votar (todavía).

Utilizo un método estático y una declaración swith para devolver exactamente lo que quiero. En la base de datos, guardo tinyint y mi código solo usa la enumeración real, por lo que las cadenas son para requisitos de IU. Después de numerosas pruebas, esto resultó en el mejor rendimiento y el mayor control sobre la salida.

public static string ToSimpleString(this enum)
{
     switch (enum)
     {
         case ComplexForms:
             return "ComplexForms";
             break;
     }
}

public static string ToFormattedString(this enum)
{
     switch (enum)
     {
         case ComplexForms:
             return "Complex Forms";
             break;
     }
}

Sin embargo, según algunas cuentas, esto lleva a una posible pesadilla de mantenimiento y algo de olor a código. Intento vigilar las enumeraciones que son largas y muchas enumeraciones, o las que cambian con frecuencia. De lo contrario, esta ha sido una gran solución para mí.

Tony Basallo
fuente
10

Si ha venido aquí para implementar un "Enum" simple pero cuyos valores son cadenas en lugar de ints, esta es la solución más simple:

    public sealed class MetricValueList
    {
        public static readonly string Brand = "A4082457-D467-E111-98DC-0026B9010912";
        public static readonly string Name = "B5B5E167-D467-E111-98DC-0026B9010912";
    }

Implementación:

var someStringVariable = MetricValueList.Brand;
Grinn
fuente
2
Probablemente sea mejor hacer las variables consts en lugar de usar static readonly.
AndyGeek
1
los consts no son buenos para las clases de acceso público, ya que se hornean en tiempo de compilación, no puede reemplazar una DLL de terceros sin volver a compilar todo el código con consts. La compensación de rendimiento de consts vs static readonly es insignificante.
Kristian Williams el
7

Cuando me enfrento a este problema, hay un par de preguntas que primero trato de encontrar las respuestas:

  • ¿Los nombres de mis valores de enumeración son lo suficientemente amigables para el propósito o necesito proporcionar valores más amigables?
  • ¿Necesito hacer un viaje de ida y vuelta? Es decir, ¿tendré que tomar valores de texto y analizarlos en valores de enumeración?
  • ¿Es esto algo que necesito hacer para muchas enumeraciones en mi proyecto, o solo una?
  • ¿En qué tipo de elementos de la interfaz de usuario presentaré esta información? En particular, ¿estaré vinculado a la interfaz de usuario o utilizaré hojas de propiedades?
  • ¿Esto necesita ser localizable?

La forma más sencilla de hacerlo es con Enum.GetValue(y admite el uso de ida y vuelta Enum.Parse). A menudo también vale la pena construir un TypeConverter, como sugiere Steve Mitcham, para soportar el enlace de la interfaz de usuario. (No es necesario construir una TypeConvertercuando se usan hojas de propiedades, que es una de las cosas buenas de las hojas de propiedades. Aunque Dios sabe que tienen sus propios problemas).

En general, si las respuestas a las preguntas anteriores sugieren que eso no va a funcionar, mi próximo paso es crear y completar una estática Dictionary<MyEnum, string>, o posiblemente una Dictionary<Type, Dictionary<int, string>>. Tiendo a omitir el paso intermedio de decorar-el-código-con-atributos porque lo que viene a continuación es la necesidad de cambiar los valores amigables después del despliegue (a menudo, pero no siempre, debido a la localización).

Robert Rossney
fuente
7

Quería publicar esto como un comentario a la publicación citada a continuación, pero no pude porque no tengo suficiente representante, así que no desestime el voto. El código contenía un error y quería señalar esto a las personas que intentan usar esta solución:

[TypeConverter(typeof(CustomEnumTypeConverter(typeof(MyEnum))]
public enum MyEnum
{
  // The custom type converter will use the description attribute
  [Description("A custom description")]
  ValueWithCustomDescription,
  // This will be exposed exactly.
  Exact
}

debiera ser

[TypeConverter(typeof(CustomEnumTypeConverter<MyEnum>))]
public enum MyEnum
{
  // The custom type converter will use the description attribute
  [Description("A custom description")]
  ValueWithCustomDescription,

  // This will be exposed exactly.
  Exact
}

Brillant!

Haba paula
fuente
5

Mi variante

public struct Colors
{
    private String current;

    private static string red = "#ff0000";
    private static string green = "#00ff00";
    private static string blue = "#0000ff";

    private static IList<String> possibleColors; 

    public static Colors Red { get { return (Colors) red; } }
    public static Colors Green { get { return (Colors) green; } }
    public static Colors Blue { get { return (Colors) blue; } }

    static Colors()
    {
        possibleColors = new List<string>() {red, green, blue};
    }

    public static explicit operator String(Colors value)
    {
        return value.current;
    }

    public static explicit operator Colors(String value)
    {
        if (!possibleColors.Contains(value))
        {
            throw new InvalidCastException();
        }

        Colors color = new Colors();
        color.current = value;
        return color;
    }

    public static bool operator ==(Colors left, Colors right)
    {
        return left.current == right.current;
    }

    public static bool operator !=(Colors left, Colors right)
    {
        return left.current != right.current;
    }

    public bool Equals(Colors other)
    {
        return Equals(other.current, current);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (obj.GetType() != typeof(Colors)) return false;
        return Equals((Colors)obj);
    }

    public override int GetHashCode()
    {
        return (current != null ? current.GetHashCode() : 0);
    }

    public override string ToString()
    {
        return current;
    }
}

El código se ve un poco feo, pero el uso de esta estructura es bastante presentativo.

Colors color1 = Colors.Red;
Console.WriteLine(color1); // #ff0000

Colors color2 = (Colors) "#00ff00";
Console.WriteLine(color2); // #00ff00

// Colors color3 = "#0000ff"; // Compilation error
// String color4 = Colors.Red; // Compilation error

Colors color5 = (Colors)"#ff0000";
Console.WriteLine(color1 == color5); // True

Colors color6 = (Colors)"#00ff00";
Console.WriteLine(color1 == color6); // False

Además, creo que si se requieren muchas de tales enumeraciones, se podría usar la generación de código (por ejemplo, T4).

Razoomnick
fuente
4

Opción 1:

public sealed class FormsAuth
{
     public override string ToString{return "Forms Authtentication";}
}
public sealed class WindowsAuth
{
     public override string ToString{return "Windows Authtentication";}
}

public sealed class SsoAuth
{
     public override string ToString{return "SSO";}
}

y entonces

object auth = new SsoAuth(); //or whatever

//...
//...
// blablabla

DoSomethingWithTheAuth(auth.ToString());

Opcion 2:

public enum AuthenticationMethod
{
        FORMS = 1,
        WINDOWSAUTHENTICATION = 2,
        SINGLESIGNON = 3
}

public class MyClass
{
    private Dictionary<AuthenticationMethod, String> map = new Dictionary<AuthenticationMethod, String>();
    public MyClass()
    {
         map.Add(AuthenticationMethod.FORMS,"Forms Authentication");
         map.Add(AuthenticationMethod.WINDOWSAUTHENTICATION ,"Windows Authentication");
         map.Add(AuthenticationMethod.SINGLESIGNON ,"SSo Authentication");
    }
}
Pablo Retyk
fuente
4

Si piensa en el problema que estamos tratando de resolver, no es una enumeración lo que necesitamos en absoluto. Necesitamos un objeto que permita asociar un cierto número de valores entre sí; en otras palabras, definir una clase.

El patrón de enumeración de tipo seguro de Jakub Šturc es la mejor opción que veo aquí.

Míralo:

  • Tiene un constructor privado, por lo que solo la clase misma puede definir los valores permitidos.
  • Es una clase sellada, por lo que los valores no se pueden modificar a través de la herencia.
  • Es de tipo seguro, permitiendo que sus métodos requieran solo ese tipo.
  • No se incide en el rendimiento de la reflexión al acceder a los valores.
  • Y, por último, se puede modificar para asociar más de dos campos, por ejemplo, un Nombre, una Descripción y un Valor numérico.
Harvo
fuente
4

para mí, el enfoque pragmático es clase dentro de clase, muestra:

public class MSEModel
{
    class WITS
    {
        public const string DATE = "5005";
        public const string TIME = "5006";
        public const string MD = "5008";
        public const string ROP = "5075";
        public const string WOB = "5073";
        public const string RPM = "7001";
... 
    }
Harveyt
fuente
4

Creé una clase base para crear enumeraciones con valores de cadena en .NET. Es solo un archivo C # que puede copiar y pegar en sus proyectos, o instalar a través del paquete NuGet llamado StringEnum . Repo de GitHub

  • Intellisense sugerirá el nombre de la enumeración si la clase se anota con el comentario xml <completitionlist>. (Funciona tanto en C # como en VB)

Demo de Intellisense

  • Uso similar a una enumeración regular:
///<completionlist cref="HexColor"/> 
class HexColor : StringEnum<HexColor>
{
    public static readonly HexColor Blue = Create("#FF0000");
    public static readonly HexColor Green = Create("#00FF00");
    public static readonly HexColor Red = Create("#000FF");
}
    // Static Parse Method
    HexColor.Parse("#FF0000") // => HexColor.Red
    HexColor.Parse("#ff0000", caseSensitive: false) // => HexColor.Red
    HexColor.Parse("invalid") // => throws InvalidOperationException

    // Static TryParse method.
    HexColor.TryParse("#FF0000") // => HexColor.Red
    HexColor.TryParse("#ff0000", caseSensitive: false) // => HexColor.Red
    HexColor.TryParse("invalid") // => null

    // Parse and TryParse returns the preexistent instances
    object.ReferenceEquals(HexColor.Parse("#FF0000"), HexColor.Red) // => true

    // Conversion from your `StringEnum` to `string`
    string myString1 = HexColor.Red.ToString(); // => "#FF0000"
    string myString2 = HexColor.Red; // => "#FF0000" (implicit cast)

Instalación:

  • Pegue la siguiente clase base StringEnum en su proyecto. ( última versión )
  • O instale el paquete StringEnum NuGet, que se basa .Net Standard 1.0para que se ejecute en .Net Core> = 1.0, .Net Framework> = 4.5, Mono> = 4.6, etc.
    /// <summary>
    /// Base class for creating string-valued enums in .NET.<br/>
    /// Provides static Parse() and TryParse() methods and implicit cast to string.
    /// </summary>
    /// <example> 
    /// <code>
    /// class Color : StringEnum &lt;Color&gt;
    /// {
    ///     public static readonly Color Blue = Create("Blue");
    ///     public static readonly Color Red = Create("Red");
    ///     public static readonly Color Green = Create("Green");
    /// }
    /// </code>
    /// </example>
    /// <typeparam name="T">The string-valued enum type. (i.e. class Color : StringEnum&lt;Color&gt;)</typeparam>
    public abstract class StringEnum<T> : IEquatable<T> where T : StringEnum<T>, new()
    {
        protected string Value;
        private static Dictionary<string, T> valueDict = new Dictionary<string, T>();
        protected static T Create(string value)
        {
            if (value == null)
                return null; // the null-valued instance is null.

            var result = new T() { Value = value };
            valueDict.Add(value, result);
            return result;
        }

        public static implicit operator string(StringEnum<T> enumValue) => enumValue.Value;
        public override string ToString() => Value;

        public static bool operator !=(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value != o2?.Value;
        public static bool operator ==(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value == o2?.Value;

        public override bool Equals(object other) => this.Value.Equals((other as T)?.Value ?? (other as string));
        bool IEquatable<T>.Equals(T other) => this.Value.Equals(other.Value);
        public override int GetHashCode() => Value.GetHashCode();

        /// <summary>
        /// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else throws InvalidOperationException.
        /// </summary>
        /// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
        /// <param name="caseSensitive">If true, the strings must match case and takes O(log n). False allows different case but is little bit slower (O(n))</param>
        public static T Parse(string value, bool caseSensitive = true)
        {
            var result = TryParse(value, caseSensitive);
            if (result == null)
                throw new InvalidOperationException((value == null ? "null" : $"'{value}'") + $" is not a valid {typeof(T).Name}");

            return result;
        }

        /// <summary>
        /// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else returns null.
        /// </summary>
        /// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
        /// <param name="caseSensitive">If true, the strings must match case. False allows different case but is slower: O(n)</param>
        public static T TryParse(string value, bool caseSensitive = true)
        {
            if (value == null) return null;
            if (valueDict.Count == 0) System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); // force static fields initialization
            if (caseSensitive)
            {
                if (valueDict.TryGetValue(value, out T item))
                    return item;
                else
                    return null;
            }
            else
            {
                // slower O(n) case insensitive search
                return valueDict.FirstOrDefault(f => f.Key.Equals(value, StringComparison.OrdinalIgnoreCase)).Value;
                // Why Ordinal? => https://esmithy.net/2007/10/15/why-stringcomparisonordinal-is-usually-the-right-choice/
            }
        }
    }
Gerardo Grignoli
fuente
3

Aquí hay otra forma de lograr la tarea de asociar cadenas con enumeraciones:

struct DATABASE {
    public enum enums {NOTCONNECTED, CONNECTED, ERROR}
    static List<string> strings =
        new List<string>() {"Not Connected", "Connected", "Error"};

    public string GetString(DATABASE.enums value) {
        return strings[(int)value];
    }
}

Este método se llama así:

public FormMain() {
    DATABASE dbEnum;

    string enumName = dbEnum.GetString(DATABASE.enums.NOTCONNECTED);
}

Puede agrupar enumeraciones relacionadas en su propia estructura. Dado que este método usa el tipo de enumeración, puede usar Intellisense para mostrar la lista de enumeraciones al hacer la GetString()llamada.

Opcionalmente, puede usar el nuevo operador en la DATABASEestructura. No usarlo significa que las cadenas Listno se asignan hasta que se realiza la primera GetString()llamada.

Russ
fuente
3

Muchas respuestas geniales aquí, pero en mi caso no resolvieron lo que quería de una "enumeración de cadenas", que era:

  1. Utilizable en una declaración de cambio, por ejemplo, interruptor (myEnum)
  2. Se puede utilizar en parámetros de función, por ejemplo, foo (tipo myEnum)
  3. Se puede hacer referencia, por ejemplo, myEnum.FirstElement
  4. Puedo usar cadenas, por ejemplo, foo ("FirstElement") == foo (myEnum.FirstElement)

1,2 y 4 en realidad se pueden resolver con un C # Typedef de una cadena (ya que las cadenas se pueden cambiar en c #)

3 se pueden resolver mediante cadenas constantes estáticas. Entonces, si tiene las mismas necesidades, este es el enfoque más simple:

public sealed class Types
{

    private readonly String name;

    private Types(String name)
    {
        this.name = name;

    }

    public override String ToString()
    {
        return name;
    }

    public static implicit operator Types(string str)
    {
        return new Types(str);

    }
    public static implicit operator string(Types str)
    {
        return str.ToString();
    }


    #region enum

    public const string DataType = "Data";
    public const string ImageType = "Image";
    public const string Folder = "Folder";
    #endregion

}

Esto permite, por ejemplo:

    public TypeArgs(Types SelectedType)
    {
        Types SelectedType = SelectedType
    }

y

public TypeObject CreateType(Types type)
    {
        switch (type)
        {

            case Types.ImageType:
              //
                break;

            case Types.DataType:
             //
                break;

        }
    }

Donde CreateType se puede llamar con una cadena o un tipo. Sin embargo, la desventaja es que cualquier cadena es automáticamente una enumeración válida , esto podría modificarse, pero luego requeriría algún tipo de función de inicio ... ¿o posiblemente haría que la conversión explícita sea interna?

Ahora, si un valor int fue importante para usted (tal vez para la velocidad de comparación), podría usar algunas ideas de la fantástica respuesta de Jakub Šturc y hacer algo un poco loco, esta es mi puñalada:

    public sealed class Types
{
    private static readonly Dictionary<string, Types> strInstance = new Dictionary<string, Types>();
    private static readonly Dictionary<int, Types> intInstance = new Dictionary<int, Types>();

    private readonly String name;
    private static int layerTypeCount = 0;
    private int value;
    private Types(String name)
    {
        this.name = name;
        value = layerTypeCount++;
        strInstance[name] = this;
        intInstance[value] = this;
    }

    public override String ToString()
    {
        return name;
    }


    public static implicit operator Types(int val)
    {
        Types result;
        if (intInstance.TryGetValue(val, out result))
            return result;
        else
            throw new InvalidCastException();
    }

    public static implicit operator Types(string str)
    {
        Types result;
        if (strInstance.TryGetValue(str, out result))
        {
            return result;
        }
        else
        {
            result = new Types(str);
            return result;
        }

    }
    public static implicit operator string(Types str)
    {
        return str.ToString();
    }

    public static bool operator ==(Types a, Types b)
    {
        return a.value == b.value;
    }
    public static bool operator !=(Types a, Types b)
    {
        return a.value != b.value;
    }

    #region enum

    public const string DataType = "Data";
    public const string ImageType = "Image";

    #endregion

}

pero por supuesto "Tipos bob = 4;" no tendría sentido a menos que los hayas inicializado primero, lo que podría derrotar el punto ...

Pero en teoría, Tipo A == Tipo B sería más rápido ...

chrispepper1989
fuente
3

Si te estoy entendiendo correctamente, simplemente puedes usar .ToString () para recuperar el nombre de la enumeración del valor (suponiendo que ya esté emitido como la enumeración); Si tenía el int desnudo (digamos de una base de datos o algo), primero puede lanzarlo a la enumeración. Los dos métodos a continuación le darán el nombre de enumeración.

AuthenticationMethod myCurrentSetting = AuthenticationMethod.FORMS;
Console.WriteLine(myCurrentSetting); // Prints: FORMS
string name = Enum.GetNames(typeof(AuthenticationMethod))[(int)myCurrentSetting-1];
Console.WriteLine(name); // Prints: FORMS

Sin embargo, tenga en cuenta que la segunda técnica supone que está usando ints y su índice se basa en 1 (no en 0). La función GetNames también es bastante pesada en comparación, está generando una matriz completa cada vez que se llama. Como puede ver en la primera técnica, .ToString () en realidad se llama implícitamente. Ambos ya se mencionan en las respuestas, por supuesto, solo estoy tratando de aclarar las diferencias entre ellos.

WHOL
fuente
3

antiguo post pero ...

La respuesta a esto en realidad puede ser muy simple. Uso Enum.ToString () la función

Hay 6 sobrecargas de esta función, puede usar Enum.Tostring ("F") o Enum.ToString () para devolver el valor de la cadena. No hay necesidad de molestarse con nada más. Aquí hay una demostración en funcionamiento

Tenga en cuenta que esta solución puede no funcionar para todos los compiladores ( esta demostración no funciona como se esperaba ) pero al menos funciona para el compilador más reciente.

Hammad Khan
fuente
2

Bueno, después de leer todo lo anterior, siento que los muchachos han complicado demasiado el tema de transformar los enumeradores en cadenas. Me gustó la idea de tener atributos sobre campos enumerados, pero creo que los atributos se usan principalmente para metadatos, pero en su caso, creo que todo lo que necesita es algún tipo de localización.

public enum Color 
{ Red = 1, Green = 2, Blue = 3}


public static EnumUtils 
{
   public static string GetEnumResourceString(object enumValue)
    {
        Type enumType = enumValue.GetType();
        string value = Enum.GetName(enumValue.GetType(), enumValue);
        string resourceKey = String.Format("{0}_{1}", enumType.Name, value);
        string result = Resources.Enums.ResourceManager.GetString(resourceKey);
        if (string.IsNullOrEmpty(result))
        {
            result = String.Format("{0}", value);
        }
        return result;
    }
}

Ahora, si intentamos llamar al método anterior, podemos llamarlo de esta manera

public void Foo()
{
  var col = Color.Red;
  Console.WriteLine (EnumUtils.GetEnumResourceString (col));
}

Todo lo que necesita hacer es crear un archivo de recursos que contenga todos los valores del enumerador y las cadenas correspondientes.

Nombre del recurso Valor del recurso
Color_Red El color de mi cadena en rojo
Color_Blue Blueeey
Color_Verde Color de Hulk

Lo que es realmente bueno de eso es que será muy útil si necesita que su aplicación esté localizada, ¡ya que todo lo que necesita hacer es crear otro archivo de recursos con su nuevo idioma! y Voe-la!

Bormagi
fuente
1

Cuando estoy en una situación así, propongo la solución a continuación.

Y como clase consumidora podrías tener

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyApp.Dictionaries
{
    class Greek
    {

        public static readonly string Alpha = "Alpha";
        public static readonly string Beta = "Beta";
        public static readonly string Gamma = "Gamma";
        public static readonly string Delta = "Delta";


        private static readonly BiDictionary<int, string> Dictionary = new BiDictionary<int, string>();


        static Greek() {
            Dictionary.Add(1, Alpha);
            Dictionary.Add(2, Beta);
            Dictionary.Add(3, Gamma);
            Dictionary.Add(4, Delta);
        }

        public static string getById(int id){
            return Dictionary.GetByFirst(id);
        }

        public static int getByValue(string value)
        {
            return Dictionary.GetBySecond(value);
        }

    }
}

Y usando un diccionario bidireccional: Basado en esto ( https://stackoverflow.com/a/255638/986160 ) suponiendo que las claves se asociarán con valores únicos en el diccionario y similares a ( https://stackoverflow.com/a / 255630/986160 ) pero un poco más elegante. Este diccionario también es enumerable y puede ir y venir de entrantes a cadenas. Además, no tiene que tener ninguna cadena en su base de código con la excepción de esta clase.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace MyApp.Dictionaries
{

    class BiDictionary<TFirst, TSecond> : IEnumerable
    {
        IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
        IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();

        public void Add(TFirst first, TSecond second)
        {
            firstToSecond.Add(first, second);
            secondToFirst.Add(second, first);
        }

        public TSecond this[TFirst first]
        {
            get { return GetByFirst(first); }
        }

        public TFirst this[TSecond second]
        {
            get { return GetBySecond(second); }
        }

        public TSecond GetByFirst(TFirst first)
        {
            return firstToSecond[first];
        }

        public TFirst GetBySecond(TSecond second)
        {
            return secondToFirst[second];
        }

        public IEnumerator GetEnumerator()
        {
            return GetFirstEnumerator();
        }

        public IEnumerator GetFirstEnumerator()
        {
            return firstToSecond.GetEnumerator();
        }

        public IEnumerator GetSecondEnumerator()
        {
            return secondToFirst.GetEnumerator();
        }
    }
}
Michail Michailidis
fuente
1

Para conjuntos de enumeraciones de cadenas más grandes, los ejemplos enumerados pueden volverse cansados. Si desea una lista de códigos de estado, o una lista de otras enumeraciones basadas en cadenas, un sistema de atributos es molesto de usar, y una clase estática con instancias de sí mismo es molesto de configurar. Para mi propia solución, utilizo plantillas T4 para que sea más fácil tener enumeraciones respaldadas por cadenas. El resultado es similar al funcionamiento de la clase HttpMethod.

Puedes usarlo así:

    string statusCode = ResponseStatusCode.SUCCESS; // Automatically converts to string when needed
    ResponseStatusCode codeByValueOf = ResponseStatusCode.ValueOf(statusCode); // Returns null if not found

    // Implements TypeConverter so you can use it with string conversion methods.
    var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(ResponseStatusCode));
    ResponseStatusCode code = (ResponseStatusCode) converter.ConvertFromInvariantString(statusCode);

    // You can get a full list of the values
    bool canIterateOverValues = ResponseStatusCode.Values.Any(); 

    // Comparisons are by value of the "Name" property. Not by memory pointer location.
    bool implementsByValueEqualsEqualsOperator = "SUCCESS" == ResponseStatusCode.SUCCESS; 

Empiezas con un archivo Enum.tt.

<#@ include file="StringEnum.ttinclude" #>


<#+
public static class Configuration
{
    public static readonly string Namespace = "YourName.Space";
    public static readonly string EnumName = "ResponseStatusCode";
    public static readonly bool IncludeComments = true;

    public static readonly object Nodes = new
    {
        SUCCESS = "The response was successful.",
        NON_SUCCESS = "The request was not successful.",
        RESOURCE_IS_DISCONTINUED = "The resource requested has been discontinued and can no longer be accessed."
    };
}
#>

Luego, agrega su archivo StringEnum.ttinclude.

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<#@ CleanupBehavior processor="T4VSHost" CleanupAfterProcessingtemplate="true" #>

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;

namespace <#= Configuration.Namespace #>
{
    /// <summary>
    /// TypeConverter implementations allow you to use features like string.ToNullable(T).
    /// </summary>
    public class <#= Configuration.EnumName #>TypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            var casted = value as string;

            if (casted != null)
            {
                var result = <#= Configuration.EnumName #>.ValueOf(casted);
                if (result != null)
                {
                    return result;
                }
            }

            return base.ConvertFrom(context, culture, value);
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            var casted = value as <#= Configuration.EnumName #>;
            if (casted != null && destinationType == typeof(string))
            {
                return casted.ToString();
            }

            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

    [TypeConverter(typeof(<#= Configuration.EnumName #>TypeConverter))]
    public class <#= Configuration.EnumName #> : IEquatable<<#= Configuration.EnumName #>>
    {
//---------------------------------------------------------------------------------------------------
// V A L U E S _ L I S T
//---------------------------------------------------------------------------------------------------
<# Write(Helpers.PrintEnumProperties(Configuration.Nodes)); #>

        private static List<<#= Configuration.EnumName #>> _list { get; set; } = null;
        public static List<<#= Configuration.EnumName #>> ToList()
        {
            if (_list == null)
            {
                _list = typeof(<#= Configuration.EnumName #>).GetFields().Where(x => x.IsStatic && x.IsPublic && x.FieldType == typeof(<#= Configuration.EnumName #>))
                    .Select(x => x.GetValue(null)).OfType<<#= Configuration.EnumName #>>().ToList();
            }

            return _list;
        }

        public static List<<#= Configuration.EnumName #>> Values()
        {
            return ToList();
        }

        /// <summary>
        /// Returns the enum value based on the matching Name of the enum. Case-insensitive search.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static <#= Configuration.EnumName #> ValueOf(string key)
        {
            return ToList().FirstOrDefault(x => string.Compare(x.Name, key, true) == 0);
        }


//---------------------------------------------------------------------------------------------------
// I N S T A N C E _ D E F I N I T I O N
//---------------------------------------------------------------------------------------------------      
        public string Name { get; private set; }
        public string Description { get; private set; }
        public override string ToString() { return this.Name; }

        /// <summary>
        /// Implcitly converts to string.
        /// </summary>
        /// <param name="d"></param>
        public static implicit operator string(<#= Configuration.EnumName #> d)
        {
            return d.ToString();
        }

        /// <summary>
        /// Compares based on the == method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator !=(<#= Configuration.EnumName #> a, <#= Configuration.EnumName #> b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Compares based on the .Equals method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator ==(<#= Configuration.EnumName #> a, <#= Configuration.EnumName #> b)
        {
            return a?.ToString() == b?.ToString();
        }

        /// <summary>
        /// Compares based on the .ToString() method
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public override bool Equals(object o)
        {
            return this.ToString() == o?.ToString();
        }

        /// <summary>
        /// Compares based on the .ToString() method
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool Equals(<#= Configuration.EnumName #> other)
        {
            return this.ToString() == other?.ToString();
        }

        /// <summary>
        /// Compares based on the .Name property
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return this.Name.GetHashCode();
        }
    }
}

<#+

public static class Helpers
{
        public static string PrintEnumProperties(object nodes)
        {
            string o = "";
            Type nodesTp = Configuration.Nodes.GetType();
            PropertyInfo[] props = nodesTp.GetProperties().OrderBy(p => p.Name).ToArray();

            for(int i = 0; i < props.Length; i++)
            {
                var prop = props[i];
                if (Configuration.IncludeComments)
                {
                    o += "\r\n\r\n";
                    o += "\r\n        ///<summary>";
                    o += "\r\n        /// "+Helpers.PrintPropertyValue(prop, Configuration.Nodes);
                    o += "\r\n        ///</summary>";
                }

                o += "\r\n        public static readonly "+Configuration.EnumName+" "+prop.Name+ " = new "+Configuration.EnumName+"(){ Name = \""+prop.Name+"\", Description = "+Helpers.PrintPropertyValue(prop, Configuration.Nodes)+ "};";
            }

            o += "\r\n\r\n";

            return o;
        }

        private static Dictionary<string, string> GetValuesMap()
        {
            Type nodesTp = Configuration.Nodes.GetType();
            PropertyInfo[] props= nodesTp.GetProperties();
            var dic = new Dictionary<string,string>();
            for(int i = 0; i < props.Length; i++)
            {
                var prop = nodesTp.GetProperties()[i];
                dic[prop.Name] = prop.GetValue(Configuration.Nodes).ToString();
            }
            return dic;
        }

        public static string PrintMasterValuesMap(object nodes)
        {
            Type nodesTp = Configuration.Nodes.GetType();
            PropertyInfo[] props= nodesTp.GetProperties();
            string o = "        private static readonly Dictionary<string, string> ValuesMap = new Dictionary<string, string>()\r\n        {";
            for(int i = 0; i < props.Length; i++)
            {
                var prop = nodesTp.GetProperties()[i];
                o += "\r\n            { \""+prop.Name+"\", "+(Helpers.PrintPropertyValue(prop,Configuration.Nodes)+" },");
            }
            o += ("\r\n        };\r\n");

            return o;
        }


        public static string PrintPropertyValue(PropertyInfo prop, object objInstance)
        {
            switch(prop.PropertyType.ToString()){
                case "System.Double":
                    return prop.GetValue(objInstance).ToString()+"D";
                case "System.Float":
                    return prop.GetValue(objInstance).ToString()+"F";
                case "System.Decimal":
                    return prop.GetValue(objInstance).ToString()+"M";
                case "System.Long":
                    return prop.GetValue(objInstance).ToString()+"L";
                case "System.Boolean":
                case "System.Int16":
                case "System.Int32":
                    return prop.GetValue(objInstance).ToString().ToLowerInvariant();
                case "System.String":
                    return "\""+prop.GetValue(objInstance)+"\"";
            }

            return prop.GetValue(objInstance).ToString();
        }

        public static string _ (int numSpaces)
        {
            string o = "";
            for(int i = 0; i < numSpaces; i++){
                o += " ";
            }

            return o;
        }
}
#>

Finalmente, recompilas tu archivo Enum.tt y el resultado se ve así:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Linq;
using System.Collections.Generic;

namespace YourName.Space
{
    public class ResponseStatusCode
    {
//---------------------------------------------------------------------------------------------------
// V A L U E S _ L I S T 
//---------------------------------------------------------------------------------------------------



        ///<summary>
        /// "The response was successful."
        ///</summary>
        public static readonly ResponseStatusCode SUCCESS = new ResponseStatusCode(){ Name = "SUCCESS", Description = "The response was successful."};


        ///<summary>
        /// "The request was not successful."
        ///</summary>
        public static readonly ResponseStatusCode NON_SUCCESS = new ResponseStatusCode(){ Name = "NON_SUCCESS", Description = "The request was not successful."};


        ///<summary>
        /// "The resource requested has been discontinued and can no longer be accessed."
        ///</summary>
        public static readonly ResponseStatusCode RESOURCE_IS_DISCONTINUED = new ResponseStatusCode(){ Name = "RESOURCE_IS_DISCONTINUED", Description = "The resource requested has been discontinued and can no longer be accessed."};


        private static List<ResponseStatusCode> _list { get; set; } = null;
        public static List<ResponseStatusCode> ToList()
        {
            if (_list == null)
            {
                _list = typeof(ResponseStatusCode).GetFields().Where(x => x.IsStatic && x.IsPublic && x.FieldType == typeof(ResponseStatusCode))
                    .Select(x => x.GetValue(null)).OfType<ResponseStatusCode>().ToList();
            }

            return _list;
        }

        public static List<ResponseStatusCode> Values()
        {
            return ToList();
        }

        /// <summary>
        /// Returns the enum value based on the matching Name of the enum. Case-insensitive search.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static ResponseStatusCode ValueOf(string key)
        {
            return ToList().FirstOrDefault(x => string.Compare(x.Name, key, true) == 0);
        }


//---------------------------------------------------------------------------------------------------
// I N S T A N C E _ D E F I N I T I O N 
//---------------------------------------------------------------------------------------------------       
        public string Name { get; set; }
        public string Description { get; set; }
        public override string ToString() { return this.Name; }

        /// <summary>
        /// Implcitly converts to string.
        /// </summary>
        /// <param name="d"></param>
        public static implicit operator string(ResponseStatusCode d)
        {
            return d.ToString();
        }

        /// <summary>
        /// Compares based on the == method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator !=(ResponseStatusCode a, ResponseStatusCode b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Compares based on the .Equals method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator ==(ResponseStatusCode a, ResponseStatusCode b)
        {
            return a?.ToString() == b?.ToString();
        }

        /// <summary>
        /// Compares based on the .ToString() method
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public override bool Equals(object o)
        {
            return this.ToString() == o?.ToString();
        }

        /// <summary>
        /// Compares based on the .Name property
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return this.Name.GetHashCode();
        }
    }
}
Pangamma
fuente