Representação de string de um Enum

912

Eu tenho a seguinte enumeração:

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

O problema, porém, é que eu preciso da palavra "FORMS" quando solicito o AuthenticationMethod.FORMS e não o ID 1.

Encontrei a seguinte solução para este problema ( link ):

Primeiro, preciso criar um atributo personalizado chamado "StringValue":

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

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

    public string Value
    {
        get { return _value; }
    }

}

Então eu posso adicionar este atributo ao meu enumerador:

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

E é claro que preciso de algo para recuperar esse 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;
    }
}

Bom agora eu tenho as ferramentas para obter um valor de string para um enumerador. Posso então usá-lo assim:

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

Ok, agora tudo isso funciona como um encanto, mas acho muito trabalho. Fiquei me perguntando se existe uma solução melhor para isso.

Eu também tentei algo com um dicionário e propriedades estáticas, mas isso também não foi melhor.

user29964
fonte
8
Agradável! Eu posso usar isso para traduzir valores de enumeração para seqüências de caracteres localizadas.
Øyvind Skaar
5
Embora você possa achar isso muito longo, na verdade é uma maneira bastante flexível de fazer outras coisas. Como um dos meus colegas apontaram, isso poderia ser usado em muitos casos, para substituir Enum Helpers que os códigos do banco de dados mapa para enum valores etc ...
BenAlabaster
27
O MSDN recomenda classes de atributo de sufixo com o sufixo "Attribute". Então "class StringValueAttribute";)
serhio 30/06
14
Concordo com o BenAlabaster, este é realmente bastante flexível. Além disso, você pode fazer disso um método de extensão apenas adicionando thisna frente do Enummétodo estático. Então você pode fazer AuthenticationMethod.Forms.GetStringValue();
Justin Pihony
5
Essa abordagem usa reflexão para ler os valores dos atributos e é muito lento se você precisar chamar GetStringValue () várias vezes na minha experiência. O padrão de enumeração segura do tipo é mais rápido.
Rn222 03/03

Respostas:

868

Experimente o padrão de enumeração segura .

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

}

A conversão de tipo explícito (ou implícito) da atualização pode ser feita por

  • adicionando campo estático com mapeamento

    private static readonly Dictionary<string, AuthenticationMethod> instance = new Dictionary<string,AuthenticationMethod>();
    • Nota: Para que a inicialização dos campos "enum member" não ative uma NullReferenceException ao chamar o construtor da instância, coloque o campo Dictionary antes dos campos "enum member" na sua classe. Isso ocorre porque os inicializadores de campo estático são chamados em ordem de declaração e antes do construtor estático, criando a situação estranha e necessária, mas confusa, que o construtor de instância pode ser chamado antes que todos os campos estáticos tenham sido inicializados e antes que o construtor estático seja chamado.
  • preenchendo esse mapeamento no construtor de instância

    instance[name] = this;
  • e adicionando operador de conversão de tipo definido pelo usuário

    public static explicit operator AuthenticationMethod(string str)
    {
        AuthenticationMethod result;
        if (instance.TryGetValue(str, out result))
            return result;
        else
            throw new InvalidCastException();
    }
    
Jakub Šturc
fonte
17
Parece um enum, mas não é um enum. Eu posso imaginar isso causando alguns problemas interessantes se as pessoas começarem a tentar comparar os AuthenticationMethods. Você provavelmente também precisará sobrecarregar vários operadores de igualdade.
Ant
36
@ Ant: Eu não preciso. Como temos apenas uma instância de cada AuthenticationMethod, a igualdade de referência herdada do Object funciona bem.
Jakub Šturc 26/02/10
10
@tyriker: Compilador faz. O construtor é privado, portanto você não pode criar uma nova instância. Membros estáticos também não são acessíveis por instância.
Jakub Šturc 14/10/10
21
@ Jakub Muito interessante. Eu tive que brincar com ele para descobrir como usá-lo e perceber seus benefícios. É uma classe pública, não estática, mas não pode ser instanciada e você pode acessar apenas seus membros estáticos. Basicamente, ele se comporta como um enum. Mas a melhor parte ... os membros estáticos são digitados da classe e não uma sequência genérica ou int. É um ... [espere por isso] ... digite enum seguro! Obrigado por me ajudar a entender.
tyriker
6
@kiran Eu publiquei uma versão ligeiramente modificada da resposta de Jakub Šturc abaixo que permite que ele seja usado com declarações switch-case, então agora não há nenhuma desvantagem para esta abordagem :)
deadlydog
228

Use método

Enum.GetName(Type MyEnumType,  object enumvariable)  

como em (Assume que Shipperé um Enum definido)

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

Também existem muitos outros métodos estáticos na classe Enum que vale a pena investigar ...

Charles Bretana
fonte
5
Exatamente. Eu criei um atributo personalizado para uma descrição de string, mas é porque eu quero uma versão amigável (com espaços e outros caracteres especiais) que possa ser facilmente vinculada a uma ComboBox ou algo assim.
lc.
5
Enum.GetName reflete os nomes dos campos na enumeração - o mesmo que .ToString (). Se o desempenho é um problema, pode ser um problema. Eu não me preocuparia, a menos que você esteja convertendo muitas enumerações.
8119 Keith
8
Outra opção a considerar, se você precisar de uma enumeração com funcionalidade extra, é "rolar o seu próprio" usando uma estrutura ... você adiciona propriedades estáticas nomeadas somente de leitura para representar os valores da enumeração que são inicializados para construtores que geram instâncias individuais da estrutura ...
Charles Bretana
1
em seguida, você pode adicionar o que outros membros da estrutura que você deseja, para implementar qualquer funcionalidade que você quer este "enum" ter ...
Charles Bretana
2
O problema aqui é que GetName não é localizável. Isso nem sempre é uma preocupação, mas é algo para estar ciente.
Joel Coehoorn
79

Você pode referenciar o nome em vez do valor usando ToString ()

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

A documentação está aqui:

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

... e se você nomear suas enumerações em Pascal Case (como eu - como ThisIsMyEnumValue = 1 etc.), você poderá usar uma regex muito simples para imprimir o formulário amigável:

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

que pode ser facilmente chamado de qualquer string:

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

Saídas:

Converter minha sentença de caso Crazy Pascal em caso amigável

Isso evita correr ao redor das casas, criando atributos personalizados e anexando-os às suas enumerações ou usando tabelas de pesquisa para casar um valor de enumeração com uma string amigável e, o melhor de tudo, é autogerenciado e pode ser usado em qualquer string Pascal Case que seja infinitamente mais reutilizável. Obviamente, ele não permite que você tenha um nome amigável diferente do seu enum, que sua solução fornece.

Porém, eu gosto da sua solução original para cenários mais complexos. Você poderia levar sua solução um passo adiante e tornar seu GetStringValue um método de extensão de sua enumeração e não precisaria fazer referência a ela 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;
}

Você pode acessá-lo facilmente diretamente da sua instância enum:

Console.WriteLine(AuthenticationMethod.SSO.GetStringValue());
BenAlabaster
fonte
2
Isso não ajuda se o "nome amigável" precisar de um espaço. Como "Autenticação de Formulários"
Ray Booysen
4
Portanto, verifique se o enum é nomeado com maiúsculas como FormsAuthentication e insira um espaço antes de maiúsculas que não estejam no início. Não é ciência de foguetes para inserir um espaço em uma corda ...
BenAlabaster
4
O espaçamento automático dos nomes de casos Pascal se torna problemático se eles contêm abreviações que devem ser capitalizadas, XML ou GPS, por exemplo.
precisa
2
@ Richardhard, não há regex perfeito para isso, mas aqui está um que deve funcionar um pouco melhor com abreviações. "(?!^)([^A-Z])([A-Z])", "$1 $2". Então HereIsATESTse torna Here Is ATEST.
Sparebytes 12/08/2013
Não é elegente fazer esses pequenos "hacks", que são o que são. Entendo o que o OP está dizendo e estou tentando encontrar uma solução semelhante, ou seja, usando a elegância do Enums, mas conseguindo acessar rapidamente a mensagem associada. A única solução em que posso pensar é aplicar algum tipo de mapeamento entre o nome da enumeração e um valor de string, mas isso não causa problemas de manutenção dos dados da string (no entanto, torna-o prático para cenários em que você precisa ter várias regiões, etc. )
Tahir Khalid
72

Infelizmente, a reflexão para obter atributos nas enumerações é bastante lenta:

Veja esta pergunta: Alguém conhece uma maneira rápida de obter atributos personalizados em um valor enum?

O .ToString()enums também é bastante lento.

Você pode escrever métodos de extensão para enumerações:

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

Isso não é ótimo, mas será rápido e não exigirá a reflexão de atributos ou nome do campo.


Atualização C # 6

Se você pode usar o C # 6, o novo nameofoperador trabalha para enumerações, então nameof(MyEnum.WINDOWSAUTHENTICATION)será convertido "WINDOWSAUTHENTICATION"em tempo de compilação , tornando-a a maneira mais rápida de obter nomes de enumerações.

Observe que isso converterá a enum explícita em uma constante embutida, para que não funcione nas enumerações que você possui em uma variável. Assim:

nameof(AuthenticationMethod.FORMS) == "FORMS"

Mas...

var myMethod = AuthenticationMethod.FORMS;
nameof(myMethod) == "myMethod"
Keith
fonte
24
Você pode buscar os valores do atributo uma vez e colocá-los em um dicionário <MyEnum, string> para manter o aspecto declarativo.
Jon Skeet
1
Sim, foi o que acabamos fazendo em um aplicativo com muitas enumerações quando descobrimos que o reflexo era o gargalo da garrafa.
8119 Keith
Obrigado Jon e Keith, acabei usando sua sugestão de dicionário. Funciona muito bem (e rápido!).
Helge Klein
@ JonSkeet Eu sei que isso é velho. Mas como alguém conseguiria isso?
user919426
2
@ user919426: Alcançar o desejo? Colocá-los em um dicionário? Basta criar um dicionário, idealmente com um inicializador de coleção ... não está claro o que você está pedindo.
Jon tiro ao prato
59

Eu uso um método de extensão:

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

Agora decore o enumcom:

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

Quando Você ligar

AuthenticationMethod.FORMS.ToDescription()você receberá "FORMS".

Mangesh Pimpalkar
fonte
1
Eu tive que adicionar using System.ComponentModel;Além disso, esse método só funciona se você quiser que o valor String seja igual ao nome do Enum. O OP queria um valor diferente.
elcool
2
Você não quer dizer quando liga AuthenticationMethod.FORMS.ToDescription()?
precisa
41

Basta usar o ToString()método

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

Para referenciar a string Tomato, basta usar

any.Tomato.ToString();
chepe
fonte
Uau. Essa foi fácil. Eu sei que o OP queria adicionar descrições personalizadas de strings, mas era disso que eu precisava. Eu deveria ter tentado fazer isso em retrospecto, mas segui a rota Enum.GetName.
Rafe
7
Por que todo mundo está complicando demais isso?
Brent
18
@Brent Porque na maioria das vezes você tem um .ToString()valor diferente do valor de fácil utilização que você precisa.
Novitchi S
2
@ Brent - porque isso é diferente da pergunta que está sendo feita. A pergunta que está sendo feita é como você obtém essa string de uma variável à qual foi atribuído um valor enumerado. Isso é dinâmico em tempo de execução. Isso está verificando a definição do tipo e definida no tempo de execução.
Hogan
1
@Hogan - o ToString () também trabalha com variáveis: any fruit = any.Tomato; string tomato = fruit.ToString();
LiborV 14/07
29

Solução muito simples para isso com .Net 4.0 e superior. Nenhum outro código é necessário.

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

Para obter a string apenas use:

MyStatus.Active.ToString("f");

ou

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

O valor será "Ativo" ou "Arquivado".

Para ver os diferentes formatos de sequência de caracteres (o "f" acima) ao chamar, Enum.ToStringconsulte esta página Sequências de formato de enumeração

David C
fonte
28

Eu uso o atributo Descrição do espaço para nome System.ComponentModel. Simplesmente decore a enum e use este código para recuperá-lo:

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 um exemplo:

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

Esse código atende muito bem às enumerações em que você não precisa de um "Nome amigável" e retornará apenas o .ToString () da enumeração.

Ray Booysen
fonte
27

Eu realmente gosto da resposta de Jakub Šturc, mas sua desvantagem é que você não pode usá-la com uma declaração de caso de mudança. Aqui está uma versão ligeiramente modificada de sua resposta que pode ser usada com uma instrução switch:

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

Portanto, você obtém todos os benefícios da resposta de Jakub Šturc, além de podermos usá-la com uma instrução switch da seguinte maneira:

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;      
}
cão mortal
fonte
Uma solução mais curta seria remover as enumerações {} e, em vez disso, manter uma contagem estática de quantas enumerações você construiu. Isso também oferece o benefício de que você não precisa adicionar uma nova instância criada à lista de enumerações. por exemplo public static int nextAvailable { get; private set; }, no construtorthis.Value = nextAvailable++;
kjhf 5/05
Idéia interessante @kjhf. Minha preocupação, porém, seria que, se alguém reorganizar o código, o valor atribuído aos valores da enumeração também poderá mudar. Por exemplo, isso pode resultar na recuperação do valor errado da enumeração quando o valor da enumeração é salvo em um arquivo / banco de dados, a ordem das linhas "new AuthenticationMethod (...)" é alterada (por exemplo, uma é removida) e, em seguida, executando o aplicativo novamente e recuperando o valor de enum do arquivo / banco de dados; o valor da enumeração pode não corresponder ao AuthenticationMethod que foi salvo originalmente.
deadlydog
Bom ponto - embora eu espero que nesses casos específicos as pessoas não confiem no valor inteiro da enum (ou reordenem o código da enum.) - e esse valor é puramente usado como uma opção e, possivelmente, uma alternativa a .Equals () e. GetHashCode (). Se estiver preocupado, você sempre pode colocar um grande comentário com "NÃO RECORDE": p
kjhf
Você não pode simplesmente sobrecarregar o =operador para permitir que a chave funcione? Eu fiz isso no VB e agora posso usá-lo na select casedeclaração.
user1318499
@ user1318499 Não, o C # possui regras mais rígidas em relação à instrução switch que o VB. Você não pode usar instâncias de classe para a instrução Case; você só pode usar primitivos constantes.
deadlydog
13

Eu uso uma combinação de várias das sugestões acima, combinadas com algum cache. Agora, peguei a ideia de algum código que encontrei em algum lugar na rede, mas não consigo me lembrar de onde o encontrei ou o encontrei. Portanto, se alguém encontrar algo parecido, comente com a atribuição.

De qualquer forma, o uso envolve os conversores de tipo, portanto, se você está vinculando à interface do usuário, 'simplesmente funciona'. Você pode estender o padrão de Jakub para pesquisa rápida de código, inicializando do conversor de tipo nos métodos estáticos.

O uso base ficaria assim

[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
}

O código para o conversor de tipo de enum personalizado segue:

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
fonte
12

Na sua pergunta, você nunca disse que realmente precisa do valor numérico da enum em qualquer lugar.

Se você não precisa e apenas precisa de um enum do tipo string (que não é um tipo integral, portanto não pode ser uma base de enum), aqui está uma maneira:

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

você pode usar a mesma sintaxe que a enum para fazer referência a ela

if (bla == AuthenticationMethod.FORMS)

Será um pouco mais lento do que com valores numéricos (comparando cadeias em vez de números), mas no lado positivo não está usando reflexão (lenta) para acessar a cadeia.

ILIA BROUDNO
fonte
se você usar "const" em vez de "estático somente leitura", poderá usar os valores como rótulos de maiúsculas e minúsculas em uma instrução switch.
Ed N.
11

Como eu resolvi isso como um método de extensão:

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 (onde o.OrderType é uma propriedade com o mesmo nome da enumeração):

o.OrderType.GetDescription()

O que me dá uma sequência de "Novo cartão" ou "Recarregar" em vez do valor real da enumeração NewCard e Refill.

Sec
fonte
Para completar, você deve incluir uma cópia da sua classe DescriptionAttribute.
21712 Bernie White
3
Bernie, DescriptionAttribute está em System.ComponentModel
agentnega
11

Atualização: visitar esta página, 8 anos depois, depois de não tocar em C # por um longo tempo, parece que minha resposta não é mais a melhor solução. Eu realmente gosto da solução de conversor vinculada às funções de atributo.

Se você estiver lendo isso, verifique também outras respostas.
(dica: eles estão acima deste)


Como muitos de vocês, gostei muito da resposta selecionada de Jakub Šturc , mas também odeio copiar e colar códigos e tentar fazê-lo o mínimo possível.

Então, decidi que queria uma classe EnumBase da qual a maior parte da funcionalidade é herdada / incorporada, deixando-me focar no conteúdo em vez do comportamento.

O principal problema dessa abordagem baseia-se no fato de que, embora os valores de Enum sejam instâncias de tipo seguro, a interação é com a implementação estática do tipo de classe Enum. Então, com uma pequena ajuda de magia genérica, acho que finalmente consegui a combinação correta. Espero que alguém ache isso tão útil quanto eu.

Vou começar com o exemplo de Jakub, mas usando herança e 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); }
}

E aqui está a classe 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
}
Serralheiro
fonte
Você pode chamar o construtor estático derivado do construtor estático base. Ainda estou pesquisando, mas até agora não encontrei nenhum problema com ele: stackoverflow.com/questions/55290034/…
Cory-G
10

Concordo com Keith, mas ainda não posso votar.

Eu uso um método estático e uma declaração swith para retornar exatamente o que eu quero. No banco de dados, armazeno tinyint e meu código usa apenas a enumeração real, portanto, as strings são para requisitos de interface do usuário. Após numerosos testes, isso resultou no melhor desempenho e maior controle sobre a saída.

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

No entanto, por algumas contas, isso leva a um possível pesadelo de manutenção e a algum cheiro de código. Eu tento ficar de olho em enums que são longas e muitas, ou que mudam com frequência. Caso contrário, esta tem sido uma ótima solução para mim.

Tony Basallo
fonte
10

Se você veio aqui procurando implementar um "Enum" simples, mas cujos valores são cadeias de caracteres em vez de ints, aqui está a solução mais simples:

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

Implementação:

var someStringVariable = MetricValueList.Brand;
Grinn
fonte
2
Provavelmente é melhor fazer com que as variáveis ​​conste em vez de usar static readonly.
AndyGeek
1
consts não são bons para classes acessíveis ao público, pois são executados em tempo de compilação, você não pode substituir uma DLL de terceiros sem recompilar todo o código com consts. O deslocamento de desempenho de consts vs estático somente leitura é insignificante.
21417 Kristian Williams
7

Quando me deparo com esse problema, há algumas perguntas para as quais tento encontrar as respostas primeiro:

  • Os nomes dos meus valores enum são suficientemente amigáveis ​​para o objetivo ou preciso fornecer valores mais amigáveis?
  • Preciso viajar de ida e volta? Ou seja, precisarei pegar valores de texto e analisá-los em valores de enumeração?
  • Isso é algo que preciso fazer para muitas enumerações no meu projeto, ou apenas uma?
  • Em que tipo de elementos da interface do usuário apresentarei essas informações - em particular, vincularei a interface do usuário ou utilizarei folhas de propriedades?
  • Isso precisa ser localizável?

A maneira mais simples de fazer isso é com Enum.GetValue(e suporta o uso de round-trip Enum.Parse). Também vale a pena construir um TypeConverter, como sugere Steve Mitcham, para suportar a ligação à interface do usuário. (Não é necessário criar um TypeConverterquando você estiver usando folhas de propriedades, o que é uma das coisas boas sobre folhas de propriedades. Embora o senhor saiba que eles têm seus próprios problemas.)

Em geral, se as respostas às perguntas acima sugerem que isso não vai funcionar, meu próximo passo é criar e preencher uma estática Dictionary<MyEnum, string>, ou possivelmente uma Dictionary<Type, Dictionary<int, string>>. Costumo ignorar a etapa intermediária de decorar o código com atributos, porque o que geralmente vem a seguir é a necessidade de alterar os valores amigáveis ​​após a implantação (geralmente, mas nem sempre, devido à localização).

Robert Rossney
fonte
7

Eu queria postar isso como um comentário na postagem citada abaixo, mas não pude porque não tenho representante suficiente - por isso, não faça votos negativos. O código continha um erro e eu queria apontar isso para as pessoas que tentam usar esta solução:

[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
}

deveria estar

[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
}

Brilhante!

Paula Bean
fonte
5

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

O código parece um pouco feio, mas o uso dessa estrutura é bastante representativo.

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

Além disso, acho que, se muitas dessas enumerações forem necessárias, a geração de código (por exemplo, T4) pode ser usada.

Razoomnick
fonte
4

Opção 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";}
}

e depois

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

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

DoSomethingWithTheAuth(auth.ToString());

Opção 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
fonte
4

Se você pensa no problema que estamos tentando resolver, não é um enum que precisamos. Precisamos de um objeto que permita que um certo número de valores seja associado um ao outro; em outras palavras, para definir uma classe.

O padrão de enumeração de tipo seguro de Jakub Šturc é a melhor opção que vejo aqui.

Olhe para isso:

  • Ele possui um construtor privado, portanto apenas a própria classe pode definir os valores permitidos.
  • É uma classe selada para que os valores não possam ser modificados por herança.
  • É seguro para o tipo, permitindo que seus métodos exijam apenas esse tipo.
  • Não há impacto no desempenho da reflexão ao acessar os valores.
  • Por fim, pode ser modificado para associar mais de dois campos, por exemplo, um Nome, Descrição e um Valor numérico.
Harvo
fonte
4

para mim, a abordagem pragmática é classe dentro da classe, exemplo:

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
fonte
4

Eu criei uma classe base para criar enumerações com valor de string no .NET. É apenas um arquivo C # que você pode copiar e colar em seus projetos ou instalar via pacote NuGet chamado StringEnum . Repositório do GitHub

  • O Intellisense sugerirá o nome da enumeração se a classe for anotada com o comentário xml <completitionlist>. (Funciona em C # e VB)

Demonstração do Intellisense

  • Uso semelhante a um enum 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)

Instalação:

  • Cole a seguinte classe base StringEnum no seu projeto. ( versão mais recente )
  • Ou instale o pacote StringEnum NuGet, que se baseia .Net Standard 1.0para que seja executado em .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
fonte
3

Aqui está outra maneira de realizar a tarefa de associar seqüências de caracteres a enumerações:

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 é chamado assim:

public FormMain() {
    DATABASE dbEnum;

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

Você pode agrupar enumerações relacionadas em sua própria estrutura. Como esse método usa o tipo de enumeração, você pode usar o Intellisense para exibir a lista de enumerações ao fazer a GetString()chamada.

Opcionalmente, você pode usar o novo operador na DATABASEestrutura. Não usá-lo significa que as seqüências de caracteres Listnão são alocadas até que a primeira GetString()chamada seja feita.

Russ
fonte
3

Muitas ótimas respostas aqui, mas no meu caso não resolveram o que eu queria de um "enum de string", que era:

  1. Utilizável em uma instrução switch, por exemplo, switch (myEnum)
  2. Pode ser usado em parâmetros de função, por exemplo, foo (tipo myEnum)
  3. Pode ser referenciado, por exemplo, myEnum.FirstElement
  4. Eu posso usar strings, por exemplo, foo ("FirstElement") == foo (myEnum.FirstElement)

1,2 e 4 podem realmente ser resolvidos com um Typedef C # de uma string (uma vez que strings são selecionáveis ​​em c #)

3 pode ser resolvido por seqüências const estáticas. Portanto, se você tiver as mesmas necessidades, esta é a abordagem mais simples:

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

}

Isso permite, por exemplo:

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

e

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

            case Types.ImageType:
              //
                break;

            case Types.DataType:
             //
                break;

        }
    }

Onde CreateType pode ser chamado com uma sequência ou um tipo. No entanto, a desvantagem é que qualquer string é automaticamente um enum válido , isso pode ser modificado, mas isso exigiria algum tipo de função init ... ou possivelmente tornaria explícita a conversão interna?

Agora, se um valor int foi importante para você (talvez para velocidade de comparação), você pode usar algumas idéias da fantástica resposta de Jakub Šturc e fazer algo um pouco louco, esta é a minha facada:

    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

}

mas é claro "Tipos bob = 4;" não teria sentido a menos que você os tivesse inicializado primeiro, o que meio que derrotaria o argumento ...

Mas, em teoria, TypeA == TypeB seria mais rápido ...

chrispepper1989
fonte
3

Se estou entendendo corretamente, você pode simplesmente usar .ToString () para recuperar o nome da enumeração do valor (supondo que ela já seja convertida como enumeração); Se você teve o int nu (digamos, de um banco de dados ou algo assim), primeiro você pode convertê-lo no enum. Ambos os métodos abaixo receberão o nome da enumeração.

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

Lembre-se, porém, a segunda técnica assume que você está usando ints e seu índice é baseado em 1 (não em 0). A função GetNames também é bastante pesada em comparação, você está gerando uma matriz inteira cada vez que é chamada. Como você pode ver na primeira técnica, .ToString () é realmente chamado implicitamente. Ambos já foram mencionados nas respostas, é claro, só estou tentando esclarecer as diferenças entre eles.

WHol
fonte
3

post antigo, mas ...

A resposta para isso pode ser realmente muito simples. Use a função Enum.ToString ()

Existem 6 sobrecargas dessa função, você pode usar Enum.Tostring ("F") ou Enum.ToString () para retornar o valor da string. Não há necessidade de se preocupar com mais nada. Aqui está uma demonstração de trabalho

Observe que esta solução pode não funcionar para todos os compiladores ( esta demonstração não funciona conforme o esperado ), mas pelo menos funciona para o compilador mais recente.

Hammad Khan
fonte
2

Bem, depois de ler todas as opções acima, sinto que os caras complicaram demais a questão de transformar enumeradores em strings. Gostei da ideia de ter atributos sobre campos enumerados, mas acho que esses atributos são usados ​​principalmente para metadados, mas no seu caso, acho que tudo que você precisa é algum tipo de localização.

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

Agora, se tentarmos chamar o método acima, podemos chamá-lo desta maneira

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

Tudo que você precisa fazer é apenas criar um arquivo de recursos contendo todos os valores do enumerador e as seqüências correspondentes

Nome do Recurso Valor do Recurso
Color_Red My String Cor em vermelho
Color_Blue Blueeey
Color_Green Hulk Color

O que é realmente muito bom nisso é que será muito útil se você precisar que seu aplicativo seja localizado, pois tudo o que você precisa fazer é criar outro arquivo de recurso com seu novo idioma! e Voe-la!

Bormagi
fonte
1

Quando estou em uma situação como essa, proponho a solução abaixo.

E como uma classe consumidora, você poderia ter

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

    }
}

E usando um dicionário bidirecional: com base nisso ( https://stackoverflow.com/a/255638/986160 ), assumindo que as chaves serão associadas a valores únicos no dicionário e semelhantes a ( https://stackoverflow.com/a / 255630/986160 ), mas um pouco mais elegante. Este dicionário também é enumerável e você pode ir e voltar de ints para strings. Além disso, você não precisa ter nenhuma string na sua base de código, com exceção desta classe.

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
fonte
1

Para conjuntos de enumerações de string maiores, os exemplos listados podem se tornar cansativos. Se você deseja uma lista de códigos de status ou outras enumerações baseadas em strings, um sistema de atributos é chato de usar e uma classe estática com instâncias próprias é chata de configurar. Para minha própria solução, utilizo o modelo T4 para facilitar a enumeração por string. O resultado é semelhante ao modo como a classe HttpMethod funciona.

Você pode usá-lo assim:

    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; 

Você começa com um arquivo 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."
    };
}
#>

Em seguida, você adiciona seu arquivo 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;
        }
}
#>

Por fim, você recompila o arquivo Enum.tt e a saída fica assim:

//------------------------------------------------------------------------------
// <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
fonte