Atributos diversos interpretados pelo compilador C#

Os atributos Conditional, Obsolete, , AttributeUsage, AsyncMethodBuilder, InterpolatedStringHandler, ModuleInitializere Experimental podem ser aplicados a elementos em seu código. Eles adicionam significado semântico a esses elementos. O compilador usa esses significados semânticos para alterar sua saída e relatar possíveis erros dos desenvolvedores usando seu código.

Atributo Conditional

O Conditional atributo torna a execução de um método dependente de um identificador de pré-processamento. O Conditional atributo é um alias para ConditionalAttribute, e pode ser aplicado a um método ou a uma classe de atributo.

No exemplo a seguir, Conditional é aplicado a um método para habilitar ou desabilitar a exibição de informações de diagnóstico específicas do programa:

#define TRACE_ON
using System.Diagnostics;

namespace AttributeExamples;

public class Trace
{
    [Conditional("TRACE_ON")]
    public static void Msg(string msg)
    {
        Console.WriteLine(msg);
    }
}

public class TraceExample
{
    public static void Main()
    {
        Trace.Msg("Now in Main...");
        Console.WriteLine("Done.");
    }
}

Se o TRACE_ON identificador não estiver definido, a saída de rastreamento não será exibida. Explore por si mesmo na janela interativa.

O Conditional atributo é freqüentemente usado com o DEBUG identificador para habilitar recursos de rastreamento e registro em log para compilações de depuração, mas não em compilações de versão, como mostrado no exemplo a seguir:

[Conditional("DEBUG")]
static void DebugMethod()
{
}

Quando um método marcado como condicional é chamado, a presença ou ausência do símbolo de pré-processamento especificado determina se o compilador inclui ou omite chamadas para o método. Se o símbolo estiver definido, a chamada é incluída; caso contrário, a chamada será omitida. Um método condicional deve ser um método em uma declaração de classe ou struct e deve ter um tipo de void retorno. O uso Conditional é mais limpo, mais elegante e menos propenso a erros do que fechar métodos dentro #if…#endif de blocos.

Se um método tiver vários Conditional atributos, o compilador incluirá chamadas para o método se um ou mais símbolos condicionais forem definidos (os símbolos são logicamente vinculados usando o operador OR). No exemplo a seguir, a presença de um ou AB resulta em uma chamada de método:

[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
    // ...
}

Usando Conditional com classes de atributo

O Conditional atributo também pode ser aplicado a uma definição de classe de atributo. No exemplo a seguir, o atributo Documentation personalizado adiciona informações aos metadados se DEBUG estiver definido.

[Conditional("DEBUG")]
public class DocumentationAttribute : System.Attribute
{
    string text;

    public DocumentationAttribute(string text)
    {
        this.text = text;
    }
}

class SampleClass
{
    // This attribute will only be included if DEBUG is defined.
    [Documentation("This method displays an integer.")]
    static void DoWork(int i)
    {
        System.Console.WriteLine(i.ToString());
    }
}

Atributo Obsolete

O Obsolete atributo marca um elemento de código como não mais recomendado para uso. O uso de uma entidade marcada como obsoleta gera um aviso ou um erro. O Obsolete atributo é um atributo de uso único e pode ser aplicado a qualquer entidade que permita atributos. Obsolete é um alias para ObsoleteAttribute.

No exemplo a seguir, o Obsolete atributo é aplicado à classe A e ao método B.OldMethod. Como o segundo argumento do construtor de atributo aplicado a B.OldMethod é definido como true, esse método causa um erro de compilador, enquanto o uso da classe A produz um aviso. Ligar B.NewMethod, no entanto, não produz nenhum aviso ou erro. Por exemplo, quando você usá-lo com as definições anteriores, o código a seguir gera dois avisos e um erro:


namespace AttributeExamples
{
    [Obsolete("use class B")]
    public class A
    {
        public void Method() { }
    }

    public class B
    {
        [Obsolete("use NewMethod", true)]
        public void OldMethod() { }

        public void NewMethod() { }
    }

    public static class ObsoleteProgram
    {
        public static void Main()
        {
            // Generates 2 warnings:
            A a = new A();

            // Generate no errors or warnings:
            B b = new B();
            b.NewMethod();

            // Generates an error, compilation fails.
            // b.OldMethod();
        }
    }
}

A cadeia de caracteres fornecida como o primeiro argumento para o construtor de atributo é exibida como parte do aviso ou erro. Dois avisos para classe A são gerados: um para a declaração da referência de classe e outro para o construtor de classe. O Obsolete atributo pode ser usado sem argumentos, mas recomenda-se incluir uma explicação sobre o que usar em vez disso.

Em C# 10, você pode usar a interpolação de cadeia de caracteres constante e o nameof operador para garantir que os nomes correspondam:

public class B
{
    [Obsolete($"use {nameof(NewMethod)} instead", true)]
    public void OldMethod() { }

    public void NewMethod() { }
}

Atributo Experimental

A partir do C# 12, tipos, métodos e montagens podem ser marcados com o System.Diagnostics.CodeAnalysis.ExperimentalAttribute para indicar um recurso experimental. O compilador emite um aviso se você acessar um método ou tipo anotado com o ExperimentalAttribute. Todos os tipos declarados em um assembly ou módulo marcado com o Experimental atributo são experimentais. O compilador emite um aviso se você acessar qualquer um deles. Você pode desativar esses avisos para pilotar um recurso experimental.

Aviso

As características experimentais estão sujeitas a alterações. As APIs podem ser alteradas ou removidas em atualizações futuras. A inclusão de recursos experimentais é uma maneira de os autores de bibliotecas obterem feedback sobre ideias e conceitos para desenvolvimento futuro. Tenha muito cuidado ao usar qualquer recurso marcado como experimental.

Você pode ler mais detalhes sobre o Experimental atributo na especificação do recurso.

Atributo SetsRequiredMembers

O SetsRequiredMembers atributo informa ao compilador que um construtor define todos os required membros nessa classe ou struct. O compilador assume qualquer construtor com o System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute atributo inicializa todos os required membros. Qualquer código que invoque tal construtor não precisa de inicializadores de objeto para definir os membros necessários. Adicionar o SetsRequiredMembers atributo é útil principalmente para registros posicionais e construtores primários.

Atributo AttributeUsage

O AttributeUsage atributo determina como uma classe de atributo personalizada pode ser usada. AttributeUsageAttribute é um atributo que você aplica a definições de atributo personalizadas. O AttributeUsage atributo permite controlar:

  • A quais elementos do programa o atributo pode ser aplicado. A menos que você restrinja seu uso, um atributo pode ser aplicado a qualquer um dos seguintes elementos do programa:
    • Assemblagem
    • Módulo
    • Campo
    • Evento
    • Método
    • Param
    • Property
    • Devolver
    • Type
  • Se um atributo pode ser aplicado a um único elemento de programa várias vezes.
  • Se as classes derivadas herdam atributos.

As configurações padrão se parecem com o exemplo a seguir quando aplicadas explicitamente:

[AttributeUsage(AttributeTargets.All,
                   AllowMultiple = false,
                   Inherited = true)]
class NewAttribute : Attribute { }

Neste exemplo, a NewAttribute classe pode ser aplicada a qualquer elemento de programa suportado. Mas só pode ser aplicado uma vez a cada entidade. As classes derivadas herdam o atributo aplicado a uma classe base.

Os AllowMultiple argumentos e Inherited são opcionais, portanto, o código a seguir tem o mesmo efeito:

[AttributeUsage(AttributeTargets.All)]
class NewAttribute : Attribute { }

O primeiro AttributeUsageAttribute argumento deve ser um ou mais elementos da AttributeTargets enumeração. Vários tipos de destino podem ser vinculados com o operador OR, como mostra o exemplo a seguir:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class NewPropertyOrFieldAttribute : Attribute { }

Os atributos podem ser aplicados à propriedade ou ao campo de suporte de uma propriedade implementada automaticamente. O atributo se aplica à propriedade, a menos que você especifique o field especificador no atributo. Ambos são mostrados no exemplo a seguir:

class MyClass
{
    // Attribute attached to property:
    [NewPropertyOrField]
    public string Name { get; set; } = string.Empty;

    // Attribute attached to backing field:
    [field: NewPropertyOrField]
    public string Description { get; set; } = string.Empty;
}

Se o AllowMultiple argumento for true, o atributo resultante pode ser aplicado mais de uma vez a uma única entidade, conforme mostrado no exemplo a seguir:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
class MultiUse : Attribute { }

[MultiUse]
[MultiUse]
class Class1 { }

[MultiUse, MultiUse]
class Class2 { }

Neste caso, MultiUseAttribute pode ser aplicado repetidamente porque AllowMultiple está definido como true. Ambos os formatos mostrados para a aplicação de vários atributos são válidos.

Se Inherited for false, as classes derivadas não herdam o atributo de uma classe base atribuída. Por exemplo:

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
class NonInheritedAttribute : Attribute { }

[NonInherited]
class BClass { }

class DClass : BClass { }

Neste caso, NonInheritedAttribute não é aplicado à DClass via herança.

Você também pode usar essas palavras-chave para especificar onde um atributo deve ser aplicado. Por exemplo, você pode usar o field: especificador para adicionar um atributo ao campo de suporte de uma propriedade autoimplementada. Ou você pode usar o field:, property: ou param: especificador para aplicar um atributo a qualquer um dos elementos gerados a partir de um registro posicional. Para obter um exemplo, consulte Sintaxe posicional para definição de propriedade.

Atributo AsyncMethodBuilder

Você adiciona o atributo a um tipo que pode ser um tipo de retorno assíncrono System.Runtime.CompilerServices.AsyncMethodBuilderAttribute . O atributo especifica o tipo que cria a implementação do método assíncrono quando o tipo especificado é retornado de um método assíncrono. O AsyncMethodBuilder atributo pode ser aplicado a um tipo que:

O construtor para o AsyncMethodBuilder atributo especifica o tipo do construtor associado. O construtor deve implementar os seguintes membros acessíveis:

  • Um método estático Create() que retorna o tipo do construtor.

  • Uma propriedade legível Task que retorna o tipo de retorno assíncrono.

  • Um void SetException(Exception) método que define a exceção quando uma tarefa falha.

  • Um void SetResult() ou void SetResult(T result) método que marca a tarefa como concluída e, opcionalmente, define o resultado da tarefa

  • Um Start método com a seguinte assinatura de API:

    void Start<TStateMachine>(ref TStateMachine stateMachine)
              where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    
  • Um AwaitOnCompleted método com a seguinte assinatura:

    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion
        where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    
  • Um AwaitUnsafeOnCompleted método com a seguinte assinatura:

          public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
              where TAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion
              where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    

Você pode aprender sobre construtores de métodos assíncronos lendo sobre os seguintes construtores fornecidos pelo .NET:

Em C# 10 e posterior, o AsyncMethodBuilder atributo pode ser aplicado a um método assíncrono para substituir o construtor para esse tipo.

InterpolatedStringHandler e InterpolatedStringHandlerArguments atributos

A partir do C# 10, você usa esses atributos para especificar que um tipo é um manipulador de cadeia de caracteres interpolada. A biblioteca .NET 6 já inclui System.Runtime.CompilerServices.DefaultInterpolatedStringHandler para cenários em que você usa uma cadeia de caracteres interpolada como argumento para um string parâmetro. Você pode ter outras instâncias em que deseja controlar como as cadeias de caracteres interpoladas são processadas. Você aplica o System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute ao tipo que implementa seu manipulador. Você aplica o System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute aos parâmetros do construtor desse tipo.

Você pode aprender mais sobre como criar um manipulador de cadeia de caracteres interpolado na especificação de recurso C# 10 para melhorias de cadeia de caracteres interpolada.

Atributo ModuleInitializer

O ModuleInitializer atributo marca um método que o tempo de execução chama quando o assembly é carregado. ModuleInitializer é um alias para ModuleInitializerAttribute.

O ModuleInitializer atributo só pode ser aplicado a um método que:

  • É estático.
  • É sem parâmetros.
  • Devoluções void.
  • É acessível a partir do módulo que contém, ou seja, internal ou public.
  • Não é um método genérico.
  • Não está contido em uma classe genérica.
  • Não é uma função local.

O ModuleInitializer atributo pode ser aplicado a vários métodos. Nesse caso, a ordem em que o tempo de execução os chama é determinística, mas não especificada.

O exemplo a seguir ilustra o uso de vários métodos de inicializador de módulo. Os Init1 métodos e Init2 são executados antes Mainde , e cada um adiciona uma cadeia de caracteres à Text propriedade. Portanto, quando Main executada, a Text propriedade já tem cadeias de caracteres de ambos os métodos inicializadores.

using System;

internal class ModuleInitializerExampleMain
{
    public static void Main()
    {
        Console.WriteLine(ModuleInitializerExampleModule.Text);
        //output: Hello from Init1! Hello from Init2!
    }
}
using System.Runtime.CompilerServices;

internal class ModuleInitializerExampleModule
{
    public static string? Text { get; set; }

    [ModuleInitializer]
    public static void Init1()
    {
        Text += "Hello from Init1! ";
    }

    [ModuleInitializer]
    public static void Init2()
    {
        Text += "Hello from Init2! ";
    }
}

Os geradores de código-fonte às vezes precisam gerar código de inicialização. Os inicializadores de módulo fornecem um local padrão para esse código. Na maioria dos outros casos, você deve escrever um construtor estático em vez de um inicializador de módulo.

Atributo SkipLocalsInit

O SkipLocalsInit atributo impede que o compilador defina o .locals init sinalizador ao emitir metadados. O SkipLocalsInit atributo é um atributo de uso único e pode ser aplicado a um método, uma propriedade, uma classe, um struct, uma interface ou um módulo, mas não a um assembly. SkipLocalsInit é um alias para SkipLocalsInitAttribute.

O .locals init sinalizador faz com que o CLR inicialize todas as variáveis locais declaradas em um método para seus valores padrão. Como o compilador também garante que você nunca use uma variável antes de atribuir algum valor a ela, .locals init normalmente não é necessário. No entanto, a inicialização zero extra pode ter um impacto mensurável no desempenho em alguns cenários, como quando você usa stackalloc para alocar uma matriz na pilha. Nesses casos, você pode adicionar o SkipLocalsInit atributo. Se aplicado a um método diretamente, o atributo afeta esse método e todas as suas funções aninhadas, incluindo lambdas e funções locais. Se aplicado a um tipo ou módulo, afeta todos os métodos aninhados dentro. Esse atributo não afeta métodos abstratos, mas afeta o código gerado para a implementação.

Este atributo requer a opção de compilador AllowUnsafeBlocks . Esse requisito sinaliza que, em alguns casos, o código pode exibir memória não atribuída (por exemplo, leitura de memória alocada em pilha não inicializada).

O exemplo a seguir ilustra o efeito do SkipLocalsInit atributo em um método que usa stackalloc. O método exibe o que estava na memória quando a matriz de inteiros foi alocada.

[SkipLocalsInit]
static void ReadUninitializedMemory()
{
    Span<int> numbers = stackalloc int[120];
    for (int i = 0; i < 120; i++)
    {
        Console.WriteLine(numbers[i]);
    }
}
// output depends on initial contents of memory, for example:
//0
//0
//0
//168
//0
//-1271631451
//32767
//38
//0
//0
//0
//38
// Remaining rows omitted for brevity.

Para tentar esse código você mesmo, defina a AllowUnsafeBlocks opção do compilador em seu arquivo .csproj :

<PropertyGroup>
  ...
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Consulte também