Atributos em C#: como criar e aplicar em seu projeto

Os atributos em C# são uma característica importante da linguagem que permite aos desenvolvedores adicionar metadados a classes, métodos, propriedades, eventos, campos, interfaces, enumerações e estruturas. Esses metadados são usados para fornecer informações adicionais ao compilador e ao tempo de execução sobre o código.

Neste artigo vamos explorar os fundamentos de atributos no C#, incluindo como definir e utilizá-los em seu código. Criaremos nossos próprios atributo para uso em validação de dados. Deixo claro que sei que existem já atributos para esse fim, e que o objetivo é te dar todas as ferramentas para poder criar seus próprios atributos de acordo com suas necessidades.

Vamos lá?

Definindo um atributo

Para definir um atributo em C#, precisamos criar uma classe que herda da classe System.Attribute. Esta classe então pode ter propriedades que nos permitem passar informações ao atributo, além de métodos.

Onde o atributo pode ser aplicado é definido através do atributo AttributeUsage, recebendo como parâmetro um ou mais AttributeTargets (enum), que, pode ter os seguintes valores, entre outros:

  • All
  • Class
  • Constructor
  • Enum
  • Field
  • Property
  • Method
  • Parameter
  • Interface

Definiremos dois atributos:

  • RequiredStringAttribute: deve validar se a cadeia de caracteres difere de nulo e vazio. Em caso contrário, lançar exceção.
  • MinMaxLengthStringAttribute: deve validar o tamanho da cadeia de caracteres está entre o limite definido pelo atributo. Em caso contrário, lançar exceção.

Para cada um deles definiremos um método Validate, que obterá o valor da propriedade através de Reflection, e realizar a validação.

Abaixo está a implementação do atributo RequiredStringAttribute.

Atributo RequiredStringAttribute

Alguns pontos de atenção aqui:

  • Linha 1: atributo AttributeUsage define em quais tipos de código o atributo definido pode ser aplicado. Em nosso caso, apenas poderá ser aplicado em propriedades.
  • Linha 2: herança à classe Attribute
  • Linha 4: construtor recebendo o nome da propriedade
  • Linha 10: início da definição do método de validação, que recebe como parâmetro o objeto que contém a propriedade a ser validada

Abaixo está a implementação do atributo MinMaxLengthStringAttribute

Atributo MinMaxLengthStringAttribute

Alguns pontos de atenção aqui:

  • Linha 1: atributo AttributeUsage define em quais tipos de código o atributo definido pode ser aplicado. Em nosso caso, apenas poderá ser aplicado em propriedades.
  • Linha 2: herança à classe Attribute
  • Linha 4: construtor recebendo o tamanho mínimo, tamanho máximo, e nome da propriedade
  • Linha 15: início da definição do método de validação, que recebe como parâmetro o objeto que contém a propriedade a ser validada

Com nossos atributos definidos, vamos aplicá-los!

Aplicando um atributo

Os atributos podem ser aplicados a classes, métodos, propriedades, eventos, campos, interfaces, enumerações e estruturas usando colchetes [ ] antes do nome do tipo ou membro que você deseja aplicar o atributo.

Classe Customer, aplicando os atributos criados.

O nosso último passo é realizar a chamada aos métodos de validação.

Validando os dados

As informações de atributos podem ser recuperadas usando Reflection. A classe Type fornece vários métodos, como o GetCustomAttributes, que permite recuperar informações de atributos em tempo de execução.

Criaremos uma classe dedicada a coordenar essas ações, chamada de AttributeValidator. Utilizamos Reflection nela, então caso não tenha familiaridade, não se preocupe que explicarei brevemente o que algumas linhas de código fazem.

Classe AttributeValidator

Alguns pontos de atenção aqui:

  • Linha 3: definição do método Validate, que recebe como parâmetro o objeto a ser validado
  • Linha 5: obtenção do tipo do objeto
  • Linha 7: iteração entre as propriedades do tipo do objeto
  • Linha 9 e 10: obtenção dos atributos customizados de tipo RequiredStringAttribute e MinMaxLengthStringAttribute.
  • Linha 12: caso exista o atributo de tipo RequiredStringAttribute na propriedade, a expressão será verdadeira
  • Linha 14 e 16: obtemos o atributo a partir da primeira posição da matriz, realizamos a conversão para seu tipo específico e invocamos o método Validate.
  • Linha 19: caso exista o atributo de tipo MinMaxLenghtStringAttribute na propriedade, a expressão será verdadeira
  • Linha 21 e 23: obtemos o atributo a partir da primeira posição da matriz, realizamos a conversão para seu tipo específico e invocamos o método Validate.

Finalmente, o passo final é invocar o método do AttributeValidator no local onde quisermos validar algum objeto.

Em nosso caso, utilizamos a classe Program, onde instanciamos um objeto de tipo Customer, e invocamos o método do AttributeValidator. No código abaixo, podemos comentar e descomentar conforme a validação que queremos verificar.

Validando o objeto Customer

Uma alternativa é, ao invés de lançar erros, agregar eles na classe AttributeValidator e retornar uma matriz com todos os erros do objeto.

Reforço: esse é um exemplo simples, prático e didático. Com o conhecimento adquirido aqui, você poderá implementar lógicas mais complexas em seus atributos customizados.


Quer alavancar sua carreira como Desenvolvedor(a) .NET?

Além de Desenvolvedor .NET Sênior, eu sou instrutor de mais de 700 alunos e também tenho dezenas de mentorados.

Conheça o Método .NET Direto ao Ponto, minha plataforma com mais de 800 videoaulas, com cursos cobrindo temas relacionados a linguagem C# e Programação Orientada a Objetos, APIs REST com ASP NET Core, Microsserviços com ASP NET Core, HTML, CSS e JavaScript, Desenvolvimento Front-end com Angular, Desenvolvimento Front-end com React, JavaScript Intermediário, TypeScript, Formação Arquitetura de Software, Microsoft Azure, Agile, SQL, e muito mais.

Inclui comunidade de centenas de alunos, suporte por ela, plataforma e e-mail, atualizações regulares e muito mais.

Clique aqui para ter mais informações e garantir sua vaga


Conclusão

Os atributos no C# permitem aos desenvolvedores adicionar metadados ao código, fornecendo informações adicionais ao compilador e ao tempo de execução. Eles são fáceis de definir e aplicar e podem ser recuperados usando Reflection, sendo altamente customizáveis.