Atributos diversos interpretados pelo compilador C#
Há vários atributos que podem ser aplicados a elementos em seu código que adicionam significado semântico a esses elementos:
Conditional
: torna a execução de um método dependente de um identificador de pré-processador.Obsolete
: marca um tipo ou membro para (potencial) remoção futura.AttributeUsage
: declara os elementos de linguagem em que um atributo pode ser aplicado.AsyncMethodBuilder
: declara um topo de construtor de métodos assíncrono.InterpolatedStringHandler
: define um construtor de cadeia de caracteres interpolada para um cenário conhecido.ModuleInitializer
: declara um método que inicializa um módulo.SkipLocalsInit
: omite o código que inicializa o armazenamento de variável local como 0.UnscopedRef
: declara que uma variávelref
normalmente interpretada comoscoped
deve ser tratada como sem escopo.OverloadResolutionPriority
: adicione um atributo de desempate para influenciar a resolução de sobrecarga para sobrecargas possivelmente ambíguas.Experimental
: marca um tipo ou membro como experimental.
O compilador usa esses significados semânticos para alterar a saída e relatar possíveis erros cometidos pelos desenvolvedores que usam seu código.
Atributo Conditional
O atributo Conditional
torna a execução de um método dependente de um identificador de pré-processamento. O atributo Conditional
é um alias para ConditionalAttribute e pode ser aplicado a um método ou uma classe de atributo.
No exemplo seguinte, 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 identificador TRACE_ON
não estiver definido, a saída de rastreamento não será exibida. Explore por conta própria na janela interativa.
O atributo Conditional
é frequentemente usado com o identificador DEBUG
para habilitar os recursos de rastreamento e log em builds de depuração, mas não em builds de versão, conforme mostrado no seguinte exemplo:
[Conditional("DEBUG")]
static void DebugMethod()
{
}
Quando um método marcado como condicional é chamado, a presença ou a 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 será 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 retorno void
. Usar Conditional
é mais limpo, mais elegante e menos propenso a erros do que os métodos delimitadores dentro de blocos #if…#endif
.
Se um método tiver vários atributos Conditional
, o compilador incluirá chamadas ao método se um ou mais símbolos condicionais forem definidos (os símbolos serão vinculados logicamente entre si usando o operador OR). No seguinte exemplo, a presença de um A
ou B
resulta em uma chamada de método:
[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
// ...
}
Usar Conditional
com classes de atributo
O atributo Conditional
também pode ser aplicado a uma definição de classe de atributos. No exemplo a seguir, o atributo Documentation
personalizado adiciona informações aos metadados, se DEBUG
é 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 atributo Obsolete
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 atributo Obsolete
é um atributo de uso único e pode ser aplicado a qualquer entidade que permite atributos. Obsolete
é um alias para ObsoleteAttribute.
No exemplo a seguir, o atributo Obsolete
é aplicado à classe A
e ao método B.OldMethod
. Como o segundo argumento do construtor de atributo aplicado B.OldMethod
é definido como true
, esse método causa um erro do compilador, enquanto o uso da classe A
produz um aviso. Chamar B.NewMethod
, no entanto, não produz aviso nem erro. Por exemplo, ao usá-lo com as definições anteriores, o código a seguir gera um erro e dois avisos:
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. São gerados dois avisos para a classe A
: um para a declaração da referência de classe e outro para o construtor de classe. O atributo Obsolete
pode ser usado sem argumentos, mas é recomendável incluir uma explicação do que deve ser usado no lugar.
No C# 10, você pode usar a interpolação de cadeia de caracteres constante e o operador nameof
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 assemblies podem ser marcados com System.Diagnostics.CodeAnalysis.ExperimentalAttribute para indicar um recurso experimental. O compilador emitirá um aviso se você acessar um método ou digitar anotado com o ExperimentalAttribute. Todos os tipos declarados em um assembly ou módulo marcados com o atributo Experimental
são experimentais. O compilador emite um aviso se você acessar qualquer um deles. Você pode desabilitar esses avisos para piloto de um recurso experimental.
Aviso
Os recursos experimentais estão sujeitos a alterações. As APIs podem ser alteradas ou podem ser removidas em atualizações futuras. Incluir recursos experimentais é uma maneira de os autores da biblioteca receberem comentários sobre ideias e conceitos para desenvolvimento futuro. Tenha extrema cautela ao usar qualquer recurso marcado como experimental.
Você pode ler mais detalhes sobre o atributo Experimental
na especificação do recurso.
Atributo SetsRequiredMembers
O atributo SetsRequiredMembers
informa ao compilador que um construtor define todos os membros required
nessa classe ou estrutura. O compilador assume que qualquer construtor com o atributo System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute inicializa todos os membros required
. Qualquer código que invoque tal construtor não precisa de inicializadores de objeto para definir os membros necessários. Adicionar o atributo SetsRequiredMembers
é útil principalmente para registros posicionais e construtores primários.
Atributo AttributeUsage
O atributo AttributeUsage
determina como uma classe de atributos personalizados pode ser usada. AttributeUsageAttribute é um atributo aplicado a definições de atributo personalizado. O atributo AttributeUsage
permite que você controle:
- A quais elementos de programa o atributo pode ser aplicado. A menos que você restrinja seu uso, um atributo pode ser aplicado a qualquer um dos seguintes elementos de programa:
- Assembly
- Módulo
- Campo
- Evento
- Método
- Parâmetro
- Propriedade
- Retorno
- Tipo
- Indica se um atributo pode ser aplicado a um único elemento do programa várias vezes.
- Se classes derivadas herdam atributos.
As configurações padrão se parecem com o seguinte exemplo quando aplicadas explicitamente:
[AttributeUsage(AttributeTargets.All,
AllowMultiple = false,
Inherited = true)]
class NewAttribute : Attribute { }
Neste exemplo, a classe NewAttribute
pode ser aplicada a qualquer elemento de programa compatível. Porém, ele pode ser aplicado apenas uma vez para cada entidade. Classes derivadas herdam o atributo aplicado a uma classe base.
Os argumentos AllowMultiple e Inherited são opcionais e, portanto, o seguinte código tem o mesmo efeito:
[AttributeUsage(AttributeTargets.All)]
class NewAttribute : Attribute { }
O primeiro argumento AttributeUsageAttribute deve ser um ou mais elementos da enumeração AttributeTargets. Vários tipos de destino podem ser vinculados junto com o operador OR, como mostra o seguinte exemplo:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class NewPropertyOrFieldAttribute : Attribute { }
Os atributos podem ser aplicados à propriedade ou ao campo de suporte para uma propriedade implementada automaticamente. O atributo se aplica à propriedade, a menos que você especifique o especificador field
no atributo. Ambos são mostrados no seguinte exemplo:
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 argumento AllowMultiple for true
, o atributo resultante poderá ser aplicado mais de uma vez a uma única entidade, conforme mostrado no seguinte exemplo:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
class MultiUse : Attribute { }
[MultiUse]
[MultiUse]
class Class1 { }
[MultiUse, MultiUse]
class Class2 { }
Nesse caso, MultiUseAttribute
pode ser aplicado repetidas vezes porque AllowMultiple
está definido como true
. Os dois formatos mostrados para a aplicação de vários atributos são válidos.
Se Inherited for false
, as classes derivadas não herdarão o atributo de uma classe base atribuída. Por exemplo:
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
class NonInheritedAttribute : Attribute { }
[NonInherited]
class BClass { }
class DClass : BClass { }
Nesse caso, NonInheritedAttribute
não é aplicado para DClass
por meio de 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 implementada automaticamente. Ou você pode usar o especificador field:
, property:
ou param:
aplicar um atributo a qualquer um dos elementos gerados por meio de um registro posicional. Para obter um exemplo, confira a Sintaxe posicional para definição de propriedade.
Atributo AsyncMethodBuilder
Você adiciona o atributo System.Runtime.CompilerServices.AsyncMethodBuilderAttribute a um tipo que pode ser um tipo de retorno assíncrono. 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 atributo AsyncMethodBuilder
pode ser aplicado a um tipo que:
- Tem um método
GetAwaiter
acessível. - O objeto retornado pelo método
GetAwaiter
implementa a interface System.Runtime.CompilerServices.ICriticalNotifyCompletion.
O construtor do atributo AsyncMethodBuilder
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
Task
legível que retorna o tipo de retorno assíncrono.Um método
void SetException(Exception)
que define a exceção quando uma tarefa falha.Um método
void SetResult()
ouvoid SetResult(T result)
que marca a tarefa como concluída e, opcionalmente, define o resultado da tarefaUm método
Start
com a seguinte assinatura de API:void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
Um método
AwaitOnCompleted
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 método
AwaitUnsafeOnCompleted
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:
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<TResult>
- System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder
- System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder<TResult>
No C# 10 e versões posteriores, o atributo AsyncMethodBuilder
pode ser aplicado a um método assíncrono para substituir o construtor por aquele tipo.
Atributos InterpolatedStringHandler
e InterpolatedStringHandlerArguments
Do C# 10 em diante, você usa esses atributos para especificar que um tipo é um manipulador de cadeia de caracteres interpolada. A biblioteca do .NET 6 já inclui System.Runtime.CompilerServices.DefaultInterpolatedStringHandler para cenários em que você usa uma cadeia de caracteres interpolada como argumento para um parâmetro string
. Você pode ter outras instâncias em que deseja controlar como as cadeias de caracteres interpoladas são processadas. Você aplica System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute ao tipo que implementa seu manipulador. Você aplica System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute aos parâmetros do construtor desse tipo.
Você pode saber mais sobre como criar um manipulador de cadeia de caracteres interpolada na especificação de recursos do C# 10 para melhorias de cadeia de caracteres interpolada.
Atributo ModuleInitializer
O atributo ModuleInitializer
marca um método chamado pelo runtime quando o assembly é carregado. ModuleInitializer
é um alias para ModuleInitializerAttribute.
O atributo ModuleInitializer
só pode ser aplicado a um método que:
- É estático.
- Não tem parâmetros.
- Retorna
void
. - É acessível por meio do módulo que o contém, ou seja,
internal
oupublic
. - Não é um método genérico.
- Não está contido em uma classe genérica.
- Não é uma função local.
O atributo ModuleInitializer
pode ser aplicado a vários métodos. Nesse caso, a ordem em que o runtime os chama é determinística, mas não especificada.
O exemplo a seguir ilustra o uso de vários métodos inicializadores de módulo. Os métodos Init1
e Init2
métodos são executados antes de Main
e cada um deles adiciona uma cadeia de caracteres à propriedade Text
. Portanto, quando Main
é executado, a propriedade Text
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. 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 atributo SkipLocalsInit
impede que o compilador defina o sinalizador .locals init
ao emitir metadados. O atributo SkipLocalsInit
é 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 sinalizador .locals init
faz com que o CLR inicialize todas as variáveis locais declaradas em um método com os respectivos valores padrão delas. 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 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 atributo SkipLocalsInit
. Se aplicado diretamente a um método, o atributo afeta esse método e todas as funções aninhadas, incluindo lambdas e funções locais. Se aplicado a um tipo ou módulo, ele 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.
Esse atributo requer a opção do compilador AllowUnsafeBlocks. Esse requisito sinaliza que, em alguns casos, o código pode exibir memória não atribuída (por exemplo, leitura da memória alocada em pilha não inicializada).
O exemplo a seguir ilustra o efeito do atributo SkipLocalsInit
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 experimentar esse código por conta própria, defina a opção do compilador AllowUnsafeBlocks
no arquivo .csproj:
<PropertyGroup>
...
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
Atributo UnscopedRef
O atributo UnscopedRef
marca uma declaração de variável como sem escopo, o que significa que a referência tem permissão para escapar.
Adicione esse atributo em que o compilador trata um ref
implicitamente como scoped
:
- O parâmetro
this
para os métodos de instância destruct
. ref
parâmetros que se referem a tiposref struct
.out
parâmetros.
Aplicar System.Diagnostics.CodeAnalysis.UnscopedRefAttribute marca o elemento como sem escopo.
Atributo OverloadResolutionPriority
O OverloadResolutionPriorityAttribute permite que os autores da biblioteca prefiram uma sobrecarga em vez de outra quando duas sobrecargas podem ser ambíguas. Seu principal caso de uso é que os autores de bibliotecas escrevam sobrecargas de melhor desempenho enquanto ainda oferecem suporte ao código existente sem interrupções.
Por exemplo, você pode adicionar uma nova sobrecarga que usa ReadOnlySpan<T> para reduzir as alocações de memória:
[OverloadResolutionPriority(1)]
public void M(params ReadOnlySpan<int> s) => Console.WriteLine("Span");
// Default overload resolution priority of 0
public void M(params int[] a) => Console.WriteLine("Array");
A resolução de sobrecarga considera os dois métodos igualmente bons para alguns tipos de argumento. Para um argumento de int[]
, ele prefere a primeira sobrecarga. Para fazer com que o compilador prefira a versão ReadOnlySpan
, você pode aumentar a prioridade dessa sobrecarga. O exemplo a seguir mostra o efeito da adição do atributo:
var d = new OverloadExample();
int[] arr = [1, 2, 3];
d.M(1, 2, 3, 4); // Prints "Span"
d.M(arr); // Prints "Span" when PriorityAttribute is applied
d.M([1, 2, 3, 4]); // Prints "Span"
d.M(1, 2, 3, 4); // Prints "Span"
Todas as sobrecargas com uma prioridade mais baixa do que a prioridade de sobrecarga mais alta são removidas do conjunto de métodos aplicáveis. Métodos sem esse atributo têm a prioridade de sobrecarga definida como o padrão zero. Os autores da biblioteca devem usar esse atributo como último recurso ao adicionar uma sobrecarga de método nova e melhor. Os autores de bibliotecas devem ter uma compreensão profunda de como a resolução de sobrecarga afeta a escolha do melhor método. Caso contrário, podem ocorrer erros inesperados.