domingo, 4 de dezembro de 2011

Trabalhando com Enum em C#

Muita pessoas desconhecem o poder que um Enum possui, sem falar que as poucas pessoas que fazem uso dele, não sabem aproveitar todas as sua possibilidades.
Utilizar Enum na implementação de software server para facilitar o desenvolvimento, sem falar que com a tipagem você impede erros de programação.

Vamos a um caso básico de Enum:
Campo estado civil: Normalmente esse campo está representado diretamente como uma coluna no banco de dados, com os tipos Char, tinyInt (Falando em SqlServer), porem na implementação do sistema, não sabemos qual é o range do campo, ficamos sempre na duvida: Solteiro, Casado, Divorciado e Viuvo ou 0-Solteiro, 1-Casado, 2-Divorciado e 3-Viuvo.
Particulamente prefiro o uso de inteiros, então quando tenho que fazer campos com valores enumerados no banco sempre uso inteiros, seja para criar campos de status, estado civil etc.
Vamos a primeira implementação:
public enum EstadoCivil
{
    Solteiro = 0,
    Casado = 1,
    Divorciado = 2,
    Viuvo = 3
}
A primeira funcionalidade que fica desconhecida da maioria é o valor para cada item do Enum, quando colocamos o valor para os itens da coleção podemos recuperar fazendo uma cast simples:
Console.WriteLine("{0} - {1}"EstadoCivil.Casado.ToString(), 
(int)EstadoCivil.Casado);
ou
Console.WriteLine("{0} - {1}"EstadoCivil.Casado.ToString(), 
                  EstadoCivil.Casado.ToString("D"));


O Resultado será: "Casado - 1"
Com isso nossas classes já tem um range de valores para o campos Estado Civil, porem normalmente precisamos representar esse campo em um DropDown, para que os usuários possam escolher os valores. Nesse ponto encontramos o maior erro no uso dos Enums.

Na construção do DropDown ele simplesmente colocar na lista de itens assim:
DropDownList ddl = new DropDownList();
ddl.Items.Add(new ListItem("Solteiro""0"));
ddl.Items.Add(new ListItem("Casado""1"));
ddl.Items.Add(new ListItem("Divorciado""2"));
ddl.Items.Add(new ListItem("Viuvo""3"));
Pronto, com isso acabamos de matar qualquer controle do range do campo. Simplesmente digitamos novamente os valores, Isso pode não ficar claro para estado civil, mas pense em um campo de Status que tenha Rascunho,Incluído, Aprovado, Pago. Quando for necessário criar um novo status "Em Analise" vamos digitar em dois locais.
Fazendo assim ficaríamos livre para controlar os tipos direto no Enum.
DropDownList ddl = new DropDownList(); 
foreach (EstadoCivil estado in Enum.GetValues(typeof(EstadoCivil)))
{
   int valor  = (int)estado;
   ddl.Items.Add(new ListItem(estado.ToString(), valor.ToString()));                    
}
Só que existe um problema nessa representação, para o DropDown a palavra "Viuvo" deveria ter acentuação, então como fazer a representação desse valor para a camada de apresentação?

Utilizando DescriptionAttribute
com ele podemos definir o valor de "apresentação" para o Enum, fazendo assim:
public enum EstadoCivil
{
        [DescriptionAttribute("Solteiro")]
        Solteiro = 0,
        [DescriptionAttribute("Casado")]
        Casado = 1,
        [DescriptionAttribute("Divorciado")]
        Divorciado = 2,
        [DescriptionAttribute("Viúvo")]
        Viuvo = 3
}
Para lê DescriptionAttribute fazemos uma classe utilitária para Enums. Assim, definimos o texto de Viúvo corretamente, e utilizamos um método simples para lê esse dado.
public static class EnumUtils
    {
        public static string StringValueOf(Enum valor)
        {
            FieldInfo fio = valor.GetType().GetField(valor.ToString());
            DescriptionAttribute[] atributos = (DescriptionAttribute[])fio.GetCustomAttributes(typeof(DescriptionAttribute), false);
            if (atributos.Length > 0)
                return atributos[0].Description;
            else
                return valor.ToString();
        }
    }
Prenchedo o DropDownList assim:
foreach (EstadoCivil estado in Enum.GetValues(typeof(EstadoCivil)))
{
 int valor  = (int)estado;
        string texto = EnumUtils.StringValueOf(estado);
        ddl.Items.Add(new ListItem(texto, valor.ToString()));                    
}
Resolvido nosso problema, mas ainda podemos melhorar ;)

Gerando um compomente de descrição para o Enum
A ideia aqui e incorporar o metodo XXXX dentro do Enum assim chamaria a descrição do Enum direto, sem uso de classes utilitárias. Para isso vamos cria uma nova classe, esse método foi explicado por Stefan Sedich

    /// <summary>
    /// This attribute is used to represent a string value
    /// for a value in an enum.
    /// </summary>
    public class StringValueAttribute : Attribute
    {
 
        #region Properties
 
        /// <summary>
        /// Holds the stringvalue for a value in an enum.
        /// </summary>
        public string StringValue { getprotected set; }
 
        #endregion
 
        #region Constructor
 
        /// <summary>
        /// Constructor used to init a StringValue Attribute
        /// </summary>
        /// <param name="value"></param>
        public StringValueAttribute(string value)
        {
            this.StringValue = value;
        }
 
        #endregion
 
    }
Trocamos a identificação dos Enum para o novo tipo:
public enum EstadoCivil
{
        [StringValue("Solteiro")]
        Solteiro = 0,
        [StringValue("Casado")]
        Casado = 1,
        [StringValue("Divorciado")]
        Divorciado = 2,
        [StringValue("Viúvo")]
        Viuvo = 3
}
pronto agora podemos chamar a descrição direta do Enum:
Console.WriteLine(EstadoCivil.Viuvo.StringValueOf()); //Resultado Viúvo
É o DropDownList fica montado assim:
foreach (EstadoCivil estado in Enum.GetValues(typeof(EstadoCivil)))
{
   int valor  = (int)estado;
   string texto = estado.StringValueOf();
   ddl.Items.Add(new ListItem(texto, valor.ToString()));                    
}
Como bonus fica essa dica

Definindo valor padrão para o Enum
[DefaultValue(Solteiro)]
public enum EstadoCivil
{
   ...
Console.WriteLine(default(EstadoCivil)); //Resultado Solteiro

Por hoje é só, até a próxima


3 comentários:

  1. Show, fiz de outra maneira mais complicada que no final dá na mesma, mas o seu exemplo ficou muito mais simples.

    ResponderExcluir
  2. Veja este link para comparar performance http://www.codeproject.com/KB/dotnet/enum.aspx

    ResponderExcluir
  3. Como eu recupero o atributo "Description" de um enum no cliente se o mesmo for declarado no lado de um serviço WCF? Pelo que pude ver, esse atributo não é serializado para o cliente... Alguma sugestão?

    ResponderExcluir