Definir e ler atributos personalizados

Os atributos fornecem uma maneira de associar informações ao código de forma declarativa. Eles também podem fornecer um elemento reutilizável que pode ser aplicado a vários alvos. Considere o ObsoleteAttribute. Ele pode ser aplicado a classes, structs, métodos, construtores e muito mais. Declara que o elemento está obsoleto. Cabe então ao compilador C# procurar esse atributo e fazer alguma ação em resposta.

Neste tutorial, você aprenderá como adicionar atributos ao seu código, como criar e usar seus próprios atributos e como usar alguns atributos que são incorporados ao .NET.

Pré-requisitos

Você precisa configurar sua máquina para executar o .NET. Você pode encontrar as instruções de instalação na página Downloads do .NET . Você pode executar este aplicativo no Windows, Ubuntu Linux, macOS ou em um contêiner Docker. Você precisa instalar seu editor de código favorito. As descrições a seguir usam o Visual Studio Code, que é um editor de código aberto e plataforma cruzada. No entanto, você pode usar quaisquer ferramentas com as quais se sinta confortável.

Criar a aplicação

Agora que você instalou todas as ferramentas, crie um novo aplicativo de console .NET. Para usar o gerador de linha de comando, execute o seguinte comando em seu shell favorito:

dotnet new console

Este comando cria arquivos de projeto .NET básicos. Você executa dotnet restore para restaurar as dependências necessárias para compilar este projeto.

Não é necessário executádotnet restore porque ele é executado implicitamente por todos os comandos que exigem uma restauração para ocorrer, como dotnet new, dotnet build, dotnet run, dotnet test, dotnet publishe dotnet pack. Para desativar a restauração implícita, use a opção --no-restore.

O comando dotnet restore ainda é útil em determinados cenários em que a restauração explícita faz sentido, como compilações de integração contínua no de Serviços de DevOps do Azure ou em sistemas de compilação que precisam controlar explicitamente quando a restauração ocorre.

Para obter informações sobre como gerenciar feeds NuGet, consulte a documentação dotnet restore.

Para executar o programa, use dotnet run. Deve ver a mensagem "Olá, Mundo" exibida no console.

Adicionar atributos ao código

Em C#, atributos são classes que herdam da Attribute classe base. Qualquer classe que herda de Attribute pode ser usada como uma espécie de "tag" em outras partes de código. Por exemplo, há um atributo chamado ObsoleteAttribute. Esse atributo sinaliza que o código está obsoleto e não deve mais ser usado. Você coloca esse atributo em uma classe, por exemplo, usando colchetes.

[Obsolete]
public class MyClass
{
}

Enquanto a classe é chamada ObsoleteAttribute, só é necessário usar [Obsolete] no código. A maioria dos códigos C# segue essa convenção. Você pode usar o nome [ObsoleteAttribute] completo, se desejar.

Ao marcar uma classe obsoleta, é uma boa ideia fornecer algumas informações sobre por que ela está obsoleta e/ou o que usar. Você inclui um parâmetro string para o atributo Obsolete para fornecer essa explicação.

[Obsolete("ThisClass is obsolete. Use ThisClass2 instead.")]
public class ThisClass
{
}

A cadeia de caracteres está sendo passada como um argumento para um ObsoleteAttribute construtor, como se você estivesse escrevendo var attr = new ObsoleteAttribute("some string").

Os parâmetros para um construtor de atributo são limitados a tipos/literais simples: bool, int, double, string, Type, enums, etc e matrizes desses tipos. Não é possível usar uma expressão ou uma variável. Você é livre para usar parâmetros posicionais ou nomeados.

Crie seu próprio atributo

Você cria um atributo definindo uma nova classe que herda da Attribute classe base.

public class MySpecialAttribute : Attribute
{
}

Com o código anterior, você pode usar [MySpecial] (ou [MySpecialAttribute]) como um atributo em outro lugar na base de código.

[MySpecial]
public class SomeOtherClass
{
}

Atributos na biblioteca de classes base do .NET, como ObsoleteAttribute, ativam certos comportamentos dentro do compilador. No entanto, qualquer atributo criado atua apenas como metadados e não resulta em nenhum código dentro da classe de atributo sendo executado. Cabe a você agir sobre esses metadados em outro lugar do seu código.

Há um "truque" aqui a que precisa prestar atenção. Como mencionado anteriormente, apenas certos tipos podem ser passados como argumentos ao usar atributos. No entanto, ao criar um tipo de atributo, o compilador C# não impede que você crie esses parâmetros. No exemplo a seguir, você criou um atributo com um construtor que compila corretamente.

public class GotchaAttribute : Attribute
{
    public GotchaAttribute(Foo myClass, string str)
    {
    }
}

No entanto, você não pode usar esse construtor com sintaxe de atributo.

[Gotcha(new Foo(), "test")] // does not compile
public class AttributeFail
{
}

O código anterior causa um erro de compilador como Attribute constructor parameter 'myClass' has type 'Foo', which is not a valid attribute parameter type

Como restringir o uso de atributos

Os atributos podem ser usados nos seguintes "alvos". Os exemplos anteriores mostram-nos nas aulas, mas também podem ser utilizados em:

  • Assembleia
  • Classe
  • Construtor
  • Delegado
  • Enum
  • Evento
  • Campo
  • ParâmetroGenérico
  • Interfaz
  • Método
  • Módulo
  • Parâmetro
  • Propriedade
  • ReturnValue
  • Estrutura

Quando você cria uma classe de atributo, por padrão, o C# permite que você use esse atributo em qualquer um dos destinos de atributo possíveis. Se pretender restringir o seu atributo a determinados destinos, pode fazê-lo usando a classe AttributeUsageAttribute no seu atributo. Isso mesmo, um atributo num atributo!

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class MyAttributeForClassAndStructOnly : Attribute
{
}

Se você tentar colocar o atributo acima em algo que não é uma classe ou um struct, você receberá um erro de compilador como Attribute 'MyAttributeForClassAndStructOnly' is not valid on this declaration type. It is only valid on 'class, struct' declarations

public class Foo
{
    // if the below attribute was uncommented, it would cause a compiler error
    // [MyAttributeForClassAndStructOnly]
    public Foo()
    { }
}

Como usar atributos anexados a um elemento de código

Os atributos atuam como metadados. Sem alguma força externa, eles realmente não fazem nada.

Para encontrar e agir sobre atributos, é necessária reflexão. Reflection permite que você escreva código em C# que examina outro código. Por exemplo, você pode usar o Reflection para obter informações sobre uma classe(adicionar using System.Reflection; no cabeçalho do seu código):

TypeInfo typeInfo = typeof(MyClass).GetTypeInfo();
Console.WriteLine("The assembly qualified name of MyClass is " + typeInfo.AssemblyQualifiedName);

Isso imprime algo como: The assembly qualified name of MyClass is ConsoleApplication.MyClass, attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

Depois de ter um TypeInfo objeto (ou um MemberInfo, FieldInfo, ou outro objeto), você pode usar o GetCustomAttributes método. Esse método retorna uma coleção de Attribute objetos. Você também pode usar GetCustomAttribute e especificar um tipo de atributo.

Aqui está um exemplo de utilização de GetCustomAttributes numa instância MemberInfo para MyClass (que vimos anteriormente ter um atributo [Obsolete]).

var attrs = typeInfo.GetCustomAttributes();
foreach(var attr in attrs)
    Console.WriteLine("Attribute on MyClass: " + attr.GetType().Name);

Isso imprime na consola: Attribute on MyClass: ObsoleteAttribute. Tente adicionar outros atributos ao MyClass.

É importante notar que esses Attribute objetos são instanciados de forma lenta. Ou seja, eles não são instanciados até que você use GetCustomAttribute ou GetCustomAttributes. Eles também são instanciados todas as vezes. Chamar GetCustomAttributes duas vezes seguidas retorna duas instâncias diferentes de ObsoleteAttribute.

Atributos comuns no tempo de execução

Os atributos são usados por muitas ferramentas e estruturas. O NUnit usa atributos como [Test] e [TestFixture] que são usados pelo executor de teste do NUnit. ASP.NET MVC usa atributos como [Authorize] e fornece uma estrutura de filtro de ação para executar preocupações transversais em ações MVC. PostSharp usa a sintaxe de atributo para permitir programação orientada a aspetos em C#.

Aqui estão alguns atributos notáveis incorporados nas bibliotecas de classes base do .NET Core:

  • [Obsolete]. Este foi usado nos exemplos acima, e vive no System namespace. É útil fornecer documentação declarativa sobre uma base de código em mudança. Uma mensagem pode ser fornecida na forma de uma cadeia de caracteres, e outro parâmetro booleano pode ser usado para escalar de um aviso do compilador para um erro do compilador.
  • [Conditional]. Esse atributo está no System.Diagnostics namespace. Este atributo pode ser aplicado a métodos (ou classes de atributo). Você deve passar uma cadeia de caracteres para o construtor. Se essa cadeia de caracteres não corresponder a uma #define diretiva, o compilador C# removerá todas as chamadas para esse método (mas não o método em si). Normalmente, você usa essa técnica para fins de depuração (diagnóstico).
  • [CallerMemberName]. Esse atributo pode ser usado em parâmetros e vive no System.Runtime.CompilerServices namespace. CallerMemberName é um atributo que é usado para injetar o nome do método que está chamando outro método. É uma maneira de eliminar 'cadeias mágicas' ao implementar INotifyPropertyChanged em várias estruturas de interface do usuário. A título de exemplo:
public class MyUIClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    public void RaisePropertyChanged([CallerMemberName] string propertyName = default!)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private string? _name;
    public string? Name
    {
        get { return _name;}
        set
        {
            if (value != _name)
            {
                _name = value;
                RaisePropertyChanged();   // notice that "Name" is not needed here explicitly
            }
        }
    }
}

No código acima, você não precisa ter uma cadeia de caracteres literal "Name" . O uso de CallerMemberName previne erros relacionados com lapsos de digitação e também facilita uma refatoração/renomeação mais fluida. Os atributos trazem poder declarativo ao C#, mas são uma forma de código de metadados e não agem sozinhos.