Custom Attributes

Entendendo e Utilizando

Muito programadores que hoje iniciam seus estudos sobre o .Net se deparam com  muitos conceitos e recursos que às vezes fogem de seu conhecimento, e talvez por falta de tempo ou cobrança de prazo não conseguem se aprofundar no uso de um determinado recurso que o framework oferece.

No artigo de hoje, meu primeiro pra MSDN Magazine, que apresentar um recurso magnífico, o Custom Attributes.

O que são Attributes

Todos sabemos que os arquivos PE (Portable Executable) do .NET são compostos por código IL e por Metadados.  Veja na Figura 1 uma visualização da formação de um arquivo .NET PE.

 

Figura 1. Estrutura de um arquivo .NET PE

 

Os metadados provêm informações a respeito do arquivo para que o compilador JIT do .NET saiba como proceder.

Através do uso de Attributes podemos adicionar mais metadados, sejam para diretivas de compilação, informações para uso próprio da aplicação, etc. Esses metadados ficam disponíveis para qualquer aplicação que leia metadados do .NET.

Resumindo, um atributo é um objeto que representa algum dado que você precisa associar com algum elemento do seu programa. Complicou ? Vamos ver um exemplo. Quando se está desenvolvendo um web service, como que você informa que um determinado método estará disponível?  Através de um attribute, veja Listagem 1.

 

Listagem 1. Atributo WebMethod

[WebMethod(Description=”Este é um atributo”)]

public Boolean ValidaCPF(string s);

 

Por trás do attribute WebMethod existe uma classe interna do .NET que usará as informações passadas.

Criando um Custom Attribute

O .NET nos deixa livres para criar qualquer atributo personalizado que precisemos. Aqui, vamos criar um para ser utilizado em uma implementação do padrão Strategy. Nosso strategy irá instanciar tipos de exportação de dados;

Declarando um atributo

Os atributos são declarados em classes que devemos derivar de System.Attribute. Além de derivá-las devemos informar ao compilador pra que tipo de elementos o atributo será aplicado. Veja Listagem 2.

 

Listagem 2. Definição de um atributo

    [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]

    public class StrategyAttribute : Attribute

    {

        private TipoExportacao _tipoexportacao;

 

   

        public StrategyAttribute(TipoExportacao tipoExportacao)

        {

        this._tipoexportacao = tipoExportacao;

        }   

 

    public TipoExportacao TipoDeExportacao

        {

        get{return _tipoexportacao;}

        }   

    }

 

O atributo [AttributeUsage] indica os elementos onde nosso custom attribute será aplicado, em nosso caso apenas será utilizado para classes. Contudo existe uma lista de opções:

·         All – qualquer elemento

·         Assembly – aplicado ao próprio assembly

·         Class  - instancias de classes

·         ClassMembers – aplicado para classes, structs, enums, construtores, métodos, propriedades, campos, eventos, delegates e interfaces

·         Constructor – apenas para construtores

·         Delegate – apenas a delegates

·         Enum – apenas para Enums

·         Event – apenas para eventos

·         Field – apenas para campos

·         Interface – apenas para interfaces

·         Method – apenas para métodos

·         Module – para um único módulo

·         Parameter – aplicado a um parâmetro de um método

·         Property – aplicado a um propriedade, incluindo seu Get e Set se implementado

·         ReturnValue – aplicado ao retorno  de algum método

·         Struct – Aplicado a um struct

 

É uma convenção adicionar a palavra Attribute ao final do nome que queremos, observe, utilizamos StrategyAttribute , dessa forma o compilador irá permitir que chamemos nosso atributo por sua abreviação, que será seu nome sem a palavra “Attribute”, ou seja, “Strategy”.

Utilizando o Custom Attribute

Nossa regra de négocio é construir um strategy para exportação de dados, então vamos criar uma classe abstrata da qual os tipos de exportação vão derivar.

 

Listagem 3. Classe base

public abstract class Exportacoes

{

  public abstract void Exportar();

}

 

Nosso objetivo é exportar no formato Word, Excel e PDF. Portanto vamos agora derivar esses tipos de exportação de nossa classe Exportacoes. Veja que estaremos utilizando nosso atributo.

 

Listagem 4. Classes de exportação

    [Strategy(TipoExportacao.Word)]

    public class ExportacaoWord : Exportacoes

    {

        override public void Exportar()

        {

            System.Windows.Forms.MessageBox.Show("Exportando para Word");

        }

    }

 

    [Strategy(TipoExportacao.Excel)]

    public class ExportacaoExcel : Exportacoes

    {

        override public void Exportar()

        {

            System.Windows.Forms.MessageBox.Show("Exportando para Excel");

        }

    }

 

    [Strategy(TipoExportacao.PDF)]

    public class ExportacaoPDF : Exportacoes

    {

        override public void Exportar()

        {

            System.Windows.Forms.MessageBox.Show("Exportando para PDF");

        }

    }

 

Em cada classe foi adicionado seu Custom Attribute, que é armazenado nos metadados do assembly.

Lendo o Custom Attribute

Nosso strategy então vai se utilizar do nosso atributo para saber qual implementação utilizar.

 

Listagem 5.  Strategy utilizando custom attribute

    public class ExportStrategyFactory

    {

        private static ArrayList _registeredImplementations;

 

        static ExportStrategyFactory()

        {

            _registeredImplementations = new ArrayList();

            RegisterClass(typeof(ExportacaoWord));

            RegisterClass(typeof(ExportacaoExcel));

            RegisterClass(typeof(ExportacaoPDF));

        }

        public static void RegisterClass(Type requestStrategyImpl)

        {

            if (!requestStrategyImpl.IsSubclassOf(typeof(Exportacoes)))

                throw new Exception("O algoritmo de exportação deve derivar da classe Exportacoes");

 

            _registeredImplementations.Add(requestStrategyImpl);

        }

        public static Exportacoes Create(TipoExportacao tipoExportacao)

        {

     

            foreach (Type impl in _registeredImplementations)

            {

  

                object[] attrlist = impl.GetCustomAttributes(true);

 

             

                foreach (object attr in attrlist)

                {

                    if (attr is StrategyAttribute)

                    {

                        if (((StrategyAttribute)attr).TypeOfAlgorithm.Equals(

                                                                tipoExportacao))

                        {

                            return

                             (Exportacoes)System.Activator.CreateInstance(impl);

                        }

                    }

                }

            }

            throw new Exception("Não foi encontrada uma implementação para esse tipo de exportação");

        }

    }

 

Através de reflexão obtemos os atributos das classes registradas. Isto é visto através do método GetCustomAttributes, veja listagem 6. Não vou entrar no mérito de como implementar o strategy, ou se esta é a melhor forma ou não, quero apenas demonstrar como criar, utilizar e ler um custom attribute.

 

Listagem 6. Reflexão

object[] attrlist = impl.GetCustomAttributes(true);

 

Utilizando o Strategy

Na Listagem 7 temos o código que utiliza nosso strategy, vale mencionar que não utilizamos nenhum estrutura condicional para determinar qual o tipo de exportação seria executado, isso está encapsulado em nossa classe ExportStrategyFactory que pode ser reutilizada por todo o sistema.

 

Listagem 7. Utilizando strategy

private void button1_Click(object sender, EventArgs e)

{

  ExportStrategyFactory.Create(TipoExportacao.Word).Exportar();

}

 

private void button2_Click(object sender, EventArgs e)

{

  ExportStrategyFactory.Create(TipoExportacao.Excel).Exportar();

}

 

private void button3_Click(object sender, EventArgs e)

{

  ExportStrategyFactory.Create(TipoExportacao.PDF).Exportar();

}

Conclusões

No artigo de hoje vimos o que são Custom Attributes, como implementá-los e como acessá-los. Não entrei no mérito de como se implementar um strategy, nem menos mencionei que acessamos o strategy por uma factory. Esses são padrões de projeto que podem tornar aplicações orientadas a objeto muito mais flexíveis e responder à mudanças de forma mais rápida, caso queiram artigo sobre padrões de projeto, entrem em contato.

Agora é com vocês, tenham criatividade suficiente para fazer com uso desse incrível recurso do .NET FRAMEWORK, Custom Attributes.