Classe de Domínio (sem Enum)

Pessoal,

este é um post que eu queria ter feito há muito tempo atrás. Alguém já se percebeu alguma limitação do Enum do C#?

Como seria um Enum em C# padrão?

public enum Sexo
{
    Masculino,
    Feminino
}

Bacana… mas, além do tipo, percebia outras limitações que atrapalhavam o desenvolvimento. Conversando com o Gildeoni, pensamos em criarmos uma classe de domínio base para podermos trabalhar, de modo desconectado, com outros tipos, conversões, etc. Deste então eu tenho adotado este modelo nos projetos. Além dessa classe base, fizemos alguns métodos de extensão e uma classe para binding.

Eu não vou colocar todas as classes aqui mas o projeto está disponível para download.

Antes de colocar alguns códigos, vou colocar duas imagens para ilustrar o funcionamento da classe de domínio:

Domain Class
Imagem – Domain Class

Classe de Domínio (sem Enum)
Imagem – Uso

Para começar, a classe base. Através dela que vamos escpefícicar o tipo do dominio (int, char, string, bool), o nome do objeto e o nome real dele (atributo NameAs: para utilizarmos fazermos binding).

DomainBase.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Arquitetura35.Attributes;

namespace Arquitetura35.Domain
{
    public abstract class DomainBase<TDomain, TValue> : IEquatable<DomainBase<TDomain, TValue>>, IEquatable<TValue>
        where TDomain : DomainBase<TDomain, TValue>, new()
    {
        public TValue Valor
        {
            get { return __value; }
        }

        private TValue __value;

        public string NameAs
        {
            get
            {
                string nome = string.Empty;

                BindingFlags bindings = (BindingFlags.Public | BindingFlags.Static);
                FieldInfo[] fieldInfos = this.GetType().GetFields(bindings);

                foreach (FieldInfo fieldInfo in fieldInfos)
                {
                    object domainValue = fieldInfo.GetValue(null);

                    if (this.__value.ToString() == domainValue.ToString())
                    {
                        IgnoreAttribute ignoreAttribute = fieldInfo.GetCustomAttribute<IgnoreAttribute>();

                        if (ignoreAttribute != null)
                        {
                            break;
                        }

                        NameAsAttribute nameAsAttribute = fieldInfo.GetCustomAttribute<NameAsAttribute>();

                        if (nameAsAttribute != null)
                        {
                            nome = nameAsAttribute.Name;

                            break;
                        }
                    }
                }

                return nome;
            }
        }

        public static List<TDomain> ToList()
        {
            List<TDomain> lista = new List<TDomain>();

            Type genericType = typeof(TDomain);
            BindingFlags bindings = (BindingFlags.Public | BindingFlags.Static);
            FieldInfo[] fieldInfos = genericType.GetFields(bindings);

            foreach (FieldInfo fieldInfo in fieldInfos)
            {
                object domainValue = fieldInfo.GetValue(null);
                lista.Add((TDomain)domainValue);
            }

            return lista;
        }

        protected DomainBase()
        {

        }

        protected DomainBase(TValue value)
        {
            this.__value = value;
        }

        public bool Equals(DomainBase<TDomain, TValue> other)
        {
            if (other != null)
            {
                return (other == this);
            }

            return (false);
        }

        public bool Equals(TValue other)
        {
            IEquatable<TValue> equatable = (other as IEquatable<TValue>);

            if (equatable != null)
            {
                return (equatable.Equals(this.__value));
            }

            return (Object.Equals(this.__value, other));
        }

        public override bool Equals(object obj)
        {
            DomainBase<TDomain, TValue> myBase = (obj as DomainBase<TDomain, TValue>);

            if (myBase != null)
            {
                return (myBase == this);
            }

            return (false);
        }

        public override int GetHashCode()
        {
            if (this.__value != null)
            {
                return (this.__value.GetHashCode());
            }

            return (base.GetHashCode());
        }

        public override string ToString()
        {
            return (this.__value.ToString());
        }

        public static explicit operator TValue(DomainBase<TDomain, TValue> rightValue)
        {
            return rightValue.__value;
        }

        public static explicit operator DomainBase<TDomain, TValue>(TValue rightValue)
        {
            TDomain domain = new TDomain();

            domain.__value = rightValue;

            return (domain);
        }

        public static bool operator !=(DomainBase<TDomain, TValue> leftValue, DomainBase<TDomain, TValue> rightValue)
        {
            return (!(leftValue == rightValue));
        }

        public static bool operator ==(DomainBase<TDomain, TValue> leftValue, DomainBase<TDomain, TValue> rightValue)
        {
            bool leftIsNull = Object.ReferenceEquals(leftValue, null);
            bool rightIsNull = Object.ReferenceEquals(rightValue, null);

            if (leftIsNull && rightIsNull)
            {
                return (true);
            }
            else if (leftIsNull || rightIsNull)
            {
                return (false);
            }

            IEquatable<TValue> equatable = (leftValue.__value as IEquatable<TValue>);

            if (equatable != null)
            {
                return (equatable.Equals(rightValue.__value));
            }

            equatable = (rightValue.__value as IEquatable<TValue>);

            if (equatable != null)
            {
                return (equatable.Equals(leftValue.__value));
            }

            return (Object.Equals(leftValue.__value, rightValue.__value));
        }
    } // class DomainBase
} // namespace Arquitetura35.Domain



ExtensionMethods.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Web;
using System.Web.UI.WebControls;
using System.Web.UI;

public static class ExtensionMethods
{
    #region Object

    public static TResult To<TResult>(this object source)
    {
        return (To<TResult>(source, CultureInfo.CurrentCulture));
    }

    public static TResult To<TResult>(this object source, IFormatProvider provider)
    {
        if (source != null)
        {
            object result = Convert.ChangeType(source, typeof(TResult), provider);

            return ((TResult)result);
        }

        return (default(TResult));
    }

    #endregion

    #region Web

    public static TResult GetSelectedValue<TResult>(this ListControl listControl)
    {
        if (listControl == null)
        {
            throw (new ArgumentNullException("listControl"));
        }

        string selectedValue = listControl.SelectedValue;

        if (!String.IsNullOrEmpty(selectedValue))
        {
            TResult value = selectedValue.To<TResult>();

            return (value);
        }

        return (default(TResult));
    }

    public static void CarregarItemInicial(this DropDownList ddl)
    {
        ddl.Clear();
        ddl.AppendDataBoundItems = true;
        ddl.Items.Add(new ListItem("Selecione o item", "-1"));
    }

    public static void Popular<TClass>(this ListControl ddl, IList<TClass> tClass, string valor, string texto)
    {
        ddl.DataTextField = texto;
        ddl.DataValueField = valor;
        ddl.DataSource = tClass;
        ddl.DataBind();
    }

    public static void Clear(this ITextControl textControl)
    {
        if (textControl == null)
        {
            throw (new ArgumentNullException("textControl"));
        }

        if (textControl is System.Web.UI.WebControls.ListControl)
        {
            ((System.Web.UI.WebControls.ListControl)textControl).Items.Clear();
        }
        else
        {
            textControl.Text = String.Empty;
        }
    }

    #endregion
}



DomainBindingSource.cs

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

using Arquitetura35.Attributes;

namespace Arquitetura35
{
    /// <summary>
    /// 
    /// </summary>
    public class DomainBindingSource : Component, IListSource, IEnumerable, IEnumerable<DomainBindingSource.DomainListItem>
    {
        /// <summary>
        /// 
        /// </summary>
        public class DomainListItem
        {
            private string _campo;

            private object _valor;

            public DomainListItem()
            {

            }

            public DomainListItem(string campo, object valor)
            {
                this._campo = campo;
                this._valor = valor;
            }

            /// <summary>
            /// 
            /// </summary>
            public string Campo
            {
                get { return (this._campo ?? String.Empty); }
                set { this._campo = value; }
            }

            /// <summary>
            /// 
            /// </summary>
            public object Valor
            {
                get { return (this._valor); }
                set { this._valor = value; }
            }

            /// <summary>
            /// 
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return (this.Campo);
            }
        }

        private List<DomainListItem> list = new List<DomainListItem>();

        private Type _domainType;

        /// <summary>
        /// 
        /// </summary>
        public DomainBindingSource()
        {

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="domainType"></param>
        public DomainBindingSource(Type domainType)
        {
            DomainBindingSource.ValidateDomainType(domainType);

            this._domainType = domainType;
            this.CreateList();
        }

        /// <summary>
        /// 
        /// </summary>
        bool IListSource.ContainsListCollection
        {
            get { return true; }
        }

        /// <summary>
        /// 
        /// </summary>
        [Browsable(true)]
        [Description("Indicates the type of the System.Class that will be used as data source.")]
        public Type DomainType
        {
            get { return this._domainType; }

            set
            {
                DomainBindingSource.ValidateDomainType(value);

                if (this._domainType != value)
                {
                    this._domainType = value;
                    this.CreateList();
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        IEnumerator<DomainListItem> IEnumerable<DomainListItem>.GetEnumerator()
        {
            foreach (DomainListItem item in this.list)
            {
                yield return (item);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        IList IListSource.GetList()
        {
            return this.list;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="domainType"></param>
        /// <returns></returns>
        private static Dictionary<string, object> GetNames(Type domainType)
        {
            Dictionary<string, object> names = new Dictionary<string, object>();

            BindingFlags bindings = (BindingFlags.Public | BindingFlags.Static);

            FieldInfo[] fieldInfos = domainType.GetFields(bindings);

            foreach (FieldInfo fieldInfo in fieldInfos)
            {
                object domainValue = fieldInfo.GetValue(null);
                IgnoreAttribute ignoreAttribute = fieldInfo.GetCustomAttribute<IgnoreAttribute>();

                if (ignoreAttribute != null)
                {
                    names.Add(null, null);

                    continue;
                }

                NameAsAttribute nameAsAttribute = fieldInfo.GetCustomAttribute<NameAsAttribute>();

                if (nameAsAttribute != null)
                {
                    names.Add(nameAsAttribute.Name, domainValue);

                    continue;
                }

                names.Add(fieldInfo.Name, domainValue);
            }

            return names;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="domainType"></param>
        private static void ValidateDomainType(Type domainType)
        {
            SpecialDomainClassAttribute specialDomain = domainType.GetCustomAttribute<SpecialDomainClassAttribute>();

            if (specialDomain == null)
            {
                throw new Exception("O tipo não é uma classe de domínio especial");
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void CreateList()
        {
            this.list.Clear();

            Dictionary<string, object> names = DomainBindingSource.GetNames(this._domainType);

            foreach(KeyValuePair<string, object> pair in names)
            {
                if (pair.Key != null)
                {
                    DomainListItem listItem = new DomainListItem(pair.Key, pair.Value);
                    this.list.Add(listItem);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IEnumerator GetEnumerator()
        {
            return this.list.GetEnumerator();
        }
    }
}



Negocio.cs

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

using Arquitetura35;
using Domain;

/// <summary>
/// Summary description for Negocio
/// </summary>
public class Negocio
{
    public Negocio()
    {

    }

    public static List<DomainBindingSource.DomainListItem> RetornaTodos()
    {
        return RetornaTodosOrdenado(true);
    }

    public static List<DomainBindingSource.DomainListItem> RetornaTodosOrdenado(bool asc)
    {
        if (asc)
            return new DomainBindingSource(typeof(Status)).OrderBy(p => p.Campo).ToList();
        else
            return new DomainBindingSource(typeof(Status)).OrderByDescending(p => p.Campo).ToList();
    }
}



Existem algumas classes de atributos também que eu não vou colocar aqui, mas elas estão no projeto.

Agora, vamos ao principal, nossas classes de Domínio, Sexo e Status:


Sexo.cs

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

using Arquitetura35.Domain;
using Arquitetura35.Attributes;

namespace Domain
{
    [SpecialDomainClass]
    public class Sexo : DomainBase<Sexo, bool>
    {
        #region "  Construtores  "

        public Sexo() : base(true)
        {

        }

        protected Sexo(bool value) : base(value)
        {

        }

        #endregion

        #region "  Domínios  "

        [NameAs("Masculino")]
        public static readonly Sexo Masculino = new Sexo(true);

        [NameAs("Feminino")]
        public static readonly Sexo Feminino = new Sexo(false);

        #endregion
    }
}



Status.cs

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

using Arquitetura35.Domain;
using Arquitetura35.Attributes;

namespace Domain
{
    [SpecialDomainClass]
    public class Status : DomainBase<Status, int>
    {
        #region "  Construtores  "

        public Status() : base(1)
        {

        }

        protected Status(int value)
            : base(value)
        {

        }

        #endregion

        #region "  Domínios  "

        [NameAs("Sem Status")]
        public static readonly Status Sem_Status = new Status(1);

        [NameAs("Ativo")]
        public static readonly Status Ativo = new Status(2);

        [NameAs("Inativo")]
        public static readonly Status Inativo = new Status(3);

        #endregion
    }
}

Legal, é possível então criar “Enumeradores” com String, Char, Bool…

Vamos ao mais interessante agora, ver o uso disso no ASP.NET e no C#:

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">

        <p>1. Recuperar o nome do Domínio: <asp:Label ID="lblNomeDominio" runat="server"></asp:Label></p>

        <p>2. Conversão do valor para o Nome: <asp:Label ID="lblConversaoValor" runat="server"></asp:Label></p>

        <p>
            3. Carregar um DDL com a classe de dominio:
            <asp:DropDownList ID="ddlStatus" runat="server"></asp:DropDownList><br />
            <asp:Label ID="lblStatus" runat="server"></asp:Label><br />
            <asp:Button ID="btnEnviar" runat="server" Text="Enviar" onclick="btnEnviar_Click" />
        </p>

        <p>2. Parse de String para Domain: <asp:Label ID="lblParseDomain" runat="server"></asp:Label></p>

    </form>
</body>
</html>



Default.aspx.cs

using System;
using System.Collections.Generic;
using System.Web.UI;

using Domain;
using Arquitetura35.Domain;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            // Exemplo 1
            lblNomeDominio.Text = Status.Sem_Status.NameAs;

            // Exemplo 2
            bool sexo = true; // Masculino
            lblConversaoValor.Text = ((Sexo)sexo).NameAs;

            // Exemplo 3
            ddlStatus.CarregarItemInicial();
            ddlStatus.Popular(Negocio.RetornaTodos(), "Valor", "Campo");

            // Exemplo 4
            lblParseDomain.Text = Dominio<Sexo>.Parse("Masculino").Valor.ToString();

            // Exemplo 5
            int status = 1; // Suponha que venha do SQL Server;

            if (status == Status.Ativo.Valor) { }
            else if (status == Status.Inativo.Valor) { }

            // Exemplo 6
            List<Sexo> lstSexo = Sexo.ToList();
        }
    }

    protected void btnEnviar_Click(object sender, EventArgs e)
    {
        lblStatus.Text = string.Format("Valor: {0} | Texto: {1}",
            ddlStatus.GetSelectedValue<int>(),
            ((Status)ddlStatus.GetSelectedValue<int>()).NameAs);
    }
}

Não parei para detalhar como funciona a classe DomainBase, mas se alguém tiver alguma dúvida, pode entrar em contato comigo pelo e-mail: contato@brunokenj.net.

Basicamente, são dois atributos que a classe de dominio vai ter: NameAs e Valor, para recuperarmos o Nome (podendo ter espaço) e o Valor, respectivamente. No binding, estas são as duas propriedades utilizadas como “Texto” e “Valor”. Desde então, nunca mais usei Enum no C#. Espero que tenham gostado da solução. Agradecimentos especiais ao Gildeoni que me ajudou com esta solução.

Faça o download da solução. Lembrando: coloquei as classes dentro da pasta APP_CODE do ASP.NET. O ideal separar essas classes em projetos a parte, para poder reutilizar em todos os projetos.

abraços,

Comentário fechado.