Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
23.1 Geral
Grande parte da linguagem C# permite que o programador especifique informações declarativas sobre as entidades definidas no programa. Por exemplo, a acessibilidade de um método em uma classe é especificada decorando-a com os method_modifiers public, protected, internale private.
O C# permite que os programadores inventem novos tipos de informações declarativas, chamados de atributos. Os programadores podem anexar atributos a várias entidades do programa e recuperar informações de atributos em um ambiente de tempo de execução.
Nota: por exemplo, uma estrutura pode definir um atributo
HelpAttributeque pode ser colocado em determinados elementos do programa (como classes e métodos) para fornecer um mapeamento desses elementos do programa para sua documentação. fim da observação
Os atributos são definidos por meio da declaração de classes de atributo (§23.2), que podem ter parâmetros posicionais e nomeados (§23.2.3). Os atributos são anexados a entidades em um programa C# usando especificações de atributo (§23.3) e podem ser recuperados em tempo de execução como instâncias de atributo (§23.4).
23.2 Classes de atributo
23.2.1 Geral
Uma classe que deriva da classe abstrata System.Attribute, direta ou indiretamente, é uma classe de atributo. A declaração de uma classe de atributo define um novo tipo de atributo que pode ser colocado em entidades do programa. Por convenção, as classes de atributo são nomeadas com um sufixo de Attribute. Os usos de um atributo podem incluir ou omitir esse sufixo.
Uma declaração genérica de classe não deve ser utilizada System.Attribute como classe de base direta ou indireta.
Exemplo:
public class B : Attribute {} public class C<T> : B {} // Error – generic cannot be an attributefim do exemplo
23.2.2 Uso de atributo
O atributo AttributeUsage (§23.5.2) é usado para descrever como uma classe de atributo pode ser usada.
AttributeUsage tem um parâmetro posicional (§23.2.3) que permite que uma classe de atributo especifique os tipos de entidades de programa nas quais ela pode ser usada.
Exemplo: o exemplo a seguir define uma classe de atributo chamada
SimpleAttributeque pode ser colocada somente em class_declarations e interface_declarations e mostra vários usos doSimpleatributo.[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] public class SimpleAttribute : Attribute { ... } [Simple] class Class1 {...} [Simple] interface Interface1 {...}Embora esse atributo seja definido com o nome
SimpleAttribute, quando esse atributo é usado, o sufixoAttributepode ser omitido, resultando no nome abreviadoSimple. Assim, o exemplo acima é semanticamente equivalente ao seguinte[SimpleAttribute] class Class1 {...} [SimpleAttribute] interface Interface1 {...}fim do exemplo
AttributeUsage tem um parâmetro nomeado (§23.2.3), chamado AllowMultiple, que indica se o atributo pode ser especificado mais de uma vez para uma determinada entidade. Se AllowMultiple para uma classe de atributo for true, essa classe de atributo será uma classe de atributo multiuso e poderá ser especificada mais de uma vez em uma entidade. Se AllowMultiple para uma classe de atributo for false ou não for especificada, essa classe de atributo será uma classe de atributo de uso único e poderá ser especificada no máximo uma vez em uma entidade.
Exemplo: o exemplo a seguir define uma classe de atributo multiuso chamada
AuthorAttributee mostra uma declaração de classe com dois usos doAuthoratributo:[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class AuthorAttribute : Attribute { public string Name { get; } public AuthorAttribute(string name) => Name = name; } [Author("Brian Kernighan"), Author("Dennis Ritchie")] class Class1 { ... }fim do exemplo
AttributeUsage tem outro parâmetro nomeado (§23.2.3), chamado Inherited, que indica se o atributo, quando especificado em uma classe base, também é herdado por classes que derivam dessa classe base. Se Inherited para uma classe de atributo for true, esse atributo será herdado. Se Inherited para uma classe de atributo for false, esse atributo não será herdado. Se não for especificado, seu valor padrão será true.
Uma classe de atributo X que não tem um atributo AttributeUsage anexado a ela, como em
class X : Attribute { ... }
é equivalente ao seguinte:
[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X : Attribute { ... }
23.2.3 Parâmetros posicionais e nomeados
As classes de atributo podem ter parâmetros posicionais e parâmetros nomeados. Cada construtor de instância pública para uma classe de atributo define uma sequência válida de parâmetros posicionais para essa classe de atributo. Cada campo e propriedade de leitura/gravação pública não estáticos para uma classe de atributo define um parâmetro nomeado para a classe de atributo. Para que uma propriedade defina um parâmetro nomeado, essa propriedade deve ter um acessador get público e um acessador set público.
Exemplo: o exemplo a seguir define uma classe de atributo chamada
HelpAttributeque tem um parâmetro posicional,url, e um parâmetro nomeado,Topic. Embora não seja estático e público, a propriedadeUrlnão define um parâmetro nomeado, pois não é leitura/gravação. Dois usos desse atributo também são mostrados:[AttributeUsage(AttributeTargets.Class)] public class HelpAttribute : Attribute { public HelpAttribute(string url) // url is a positional parameter { ... } // Topic is a named parameter public string Topic { get; set; } public string Url { get; } } [Help("http://www.mycompany.com/xxx/Class1.htm")] class Class1 { } [Help("http://www.mycompany.com/xxx/Misc.htm", Topic ="Class2")] class Class2 { }fim do exemplo
23.2.4 Tipos de parâmetro de atributo
Os tipos de parâmetros posicionais e nomeados para uma classe de atributo são limitados aos tipos de parâmetro de atributo, que são:
- Um dos seguintes tipos:
bool,byte,char,double,float,int,long,sbyte,short,string,uint,ulong,ushort. - O tipo
object. - O tipo
System.Type. - Tipos enumerados.
- Matrizes unidimensionais dos tipos acima.
- Um argumento de construtor ou campo público que não tenha um desses tipos não deve ser usado como um parâmetro posicional ou nomeado em uma especificação de atributo.
Especificação do atributo 23.3
A aplicação de um atributo definido anteriormente para uma entidade de programa é chamada de especificação de atributo. Um atributo é uma informação declarativa adicional especificada para uma entidade de programa. Os atributos podem ser especificados no escopo global (para especificar atributos no assembly ou módulo que contém) e para type_declarations (§14.7), class_member_declarations (§15.3), interface_member_declarations (§19.4), struct_member_declarations (§16.3), enum_member_declarations (§20.2), accessor_declarations (§15.7.3), event_accessor_declarations (§15.8), elementos de parameter_lists (§15.6.2) e elementos de type_parameter_lists (§15.2.3).
Os atributos são especificados nas seções de atributo. Uma seção de atributo consiste em um par de colchetes, que circundam uma lista separada por vírgulas de um ou mais atributos. A ordem na qual os atributos são especificados em tal lista e a ordem na qual as seções anexadas à mesma entidade de programa são organizadas não são significativas. Por exemplo, as especificações de atributo [A][B], [B][A], [A, B], e [B, A] são equivalentes.
global_attributes
: global_attribute_section+
;
global_attribute_section
: '[' global_attribute_target_specifier attribute_list ']'
;
global_attribute_target_specifier
: global_attribute_target ':'
;
global_attribute_target
: identifier
;
attributes
: attribute_section+
;
attribute_section
: '[' attribute_target_specifier? attribute_list ']'
;
attribute_target_specifier
: attribute_target ':'
;
attribute_target
: identifier
| keyword
;
attribute_list
: attribute (',' attribute)* ','?
;
attribute
: attribute_name attribute_arguments?
;
attribute_name
: type_name
;
attribute_arguments
: '(' ')'
| '(' positional_argument_list (',' named_argument_list)? ')'
| '(' named_argument_list ')'
;
positional_argument_list
: positional_argument (',' positional_argument)*
;
positional_argument
: argument_name? attribute_argument_expression
;
named_argument_list
: named_argument (',' named_argument)*
;
named_argument
: identifier '=' attribute_argument_expression
;
attribute_argument_expression
: non_assignment_expression
;
Para a produção global_attribute_target, e no texto abaixo, o identificador deve ter uma grafia igual a assembly ou module, onde a igualdade é aquela definida em §6.4.3. Para a produção attribute_target, e no teto abaixo, o identificador deve ter uma ortografia que não seja igual a assembly ou module, usando a mesma definição de igualdade acima.
Um atributo consiste em um attribute_name e uma lista opcional de argumentos posicionais e nomeados. Os argumentos posicionais (se houver) precedem os argumentos nomeados. Um argumento posicional consiste em um attribute_argument_expression; um argumento nomeado consiste em um nome, seguido por um sinal de igual, seguido por um attribute_argument_expression, que, juntos, são limitados pelas mesmas regras da atribuição simples. A ordem dos argumentos nomeados não é significativa.
Observação: por conveniência, uma vírgula à direita é permitida em um global_attribute_section e uma attribute_section, assim como uma é permitida em um array_initializer (§17.7). fim da observação
O attribute_name identifica uma classe de atributo.
Quando um atributo é colocado no nível global, um global_attribute_target_specifier é necessário. Quando o global_attribute_target é igual a:
-
assembly— o alvo é o conjunto que o contém -
module— o destino é o módulo que o contém
Nenhum outro valor para global_attribute_target é permitido.
Os nomes de atributo attribute_target padronizados são event, field, method, param, property, return, typee typevar. Esses nomes de destino só devem ser usados nos seguintes contextos:
-
event— um evento. -
field— um campo. Um evento semelhante a um campo (ou seja, um sem acessadores) (§15.8.2) e uma propriedade implementada automaticamente (§15.7.4) também podem ter um atributo com esse destino. -
method— um construtor, finalizador, método, operador, acessadores get e set de propriedade, acessadores get e set do indexador e acessadores add e remove de eventos. Um evento semelhante a um campo (ou seja, um sem acessadores) também pode ter um atributo com esse destino. -
param— um acessador de conjunto de propriedades, um acessador de conjunto de indexadores, acessadores de adição e remoção de eventos e um parâmetro em um construtor, método e operador. -
property— uma propriedade e um indexador. -
return— um delegado, método, operador, acessador get de propriedade e acessador get indexador. -
type— um delegado, classe, struct, enumeração e interface. -
typevar— um parâmetro de tipo.
Determinados contextos permitem a especificação de um atributo em mais de um destino. Um programa pode especificar explicitamente o destino incluindo um attribute_target_specifier. Sem um attribute_target_specifier um padrão é aplicado, mas um attribute_target_specifier pode ser usado para afirmar ou substituir o padrão. Os contextos são resolvidos da seguinte forma:
- Para um atributo em uma declaração de delegado, o destino padrão é o delegado. Caso contrário, quando o attribute_target é igual a:
-
type— o destino é o delegado -
return— o destino é o valor de retorno
-
- Para um atributo em uma declaração de método, o destino padrão é o método. Caso contrário, quando o attribute_target é igual a:
-
method— o destino é o método -
return— o destino é o valor de retorno
-
- Para um atributo em uma declaração de operador, o destino padrão é o operador. Caso contrário, quando o attribute_target é igual a:
-
method— o destino é o operador -
return— o destino é o valor de retorno
-
- Para um atributo em uma declaração de acessador get para uma declaração de propriedade ou indexador, o destino padrão é o método associado. Caso contrário, quando o attribute_target é igual a:
-
method— o destino é o método associado -
return— o destino é o valor de retorno
-
- Para um atributo especificado em um acessador de conjunto para uma declaração de propriedade ou indexador, o destino padrão é o método associado. Caso contrário, quando o attribute_target é igual a:
-
method— o destino é o método associado -
param— o destino é o único parâmetro implícito
-
- Para um atributo em uma declaração de propriedade implementada automaticamente, o destino padrão é a propriedade. Caso contrário, quando o attribute_target é igual a:
-
field— o destino é o campo de suporte gerado pelo compilador para a propriedade
-
- Para um atributo especificado em uma declaração que omite event_accessor_declarations o destino padrão é a declaração de evento. Caso contrário, quando o attribute_target é igual a:
-
event— o destino é a declaração de evento -
field— o destino é o campo -
method— os destinos são os métodos
-
- No caso de uma declaração de evento que não omite event_accessor_declarations o destino padrão é o método.
-
method— o destino é o método associado -
param— o destino é o único parâmetro
-
Em todos os outros contextos, a inclusão de um attribute_target_specifier é permitida, mas desnecessária.
Exemplo: uma declaração de classe pode incluir ou omitir o especificador
type:[type: Author("Brian Kernighan")] class Class1 {} [Author("Dennis Ritchie")] class Class2 {}fim do exemplo.
Uma implementação pode aceitar outros attribute_targets, cujas finalidades são definidas pela implementação. Uma implementação que não reconheça tal attribute_target emitirá um aviso e ignorará o attribute_section que o contém.
Por convenção, as classes de atributo são nomeadas com um sufixo de Attribute. Um attribute_name pode incluir ou omitir esse sufixo. Especificamente, um attribute_name é resolvido da seguinte maneira:
- Se o identificador mais à direita do attribute_name for um identificador literal (§6.4.3), o attribute_name será resolvido como um type_name (§7.8). Se o resultado não for um tipo derivado de
System.Attribute, ocorrerá um erro em tempo de compilação. - Senão,
- O attribute_name é resolvido como um type_name (§7.8), exceto que quaisquer erros são suprimidos. Se essa resolução for bem-sucedida e resultar em um tipo derivado de
System.Attribute, o tipo será o resultado dessa etapa. - Os caracteres
Attributesão anexados ao identificador mais à direita no attribute_name e a cadeia de caracteres resultante de tokens é resolvida como um type_name (§7.8), exceto que todos os erros são suprimidos. Se essa resolução for bem-sucedida e resultar em um tipo derivado deSystem.Attribute, o tipo será o resultado dessa etapa.
- O attribute_name é resolvido como um type_name (§7.8), exceto que quaisquer erros são suprimidos. Se essa resolução for bem-sucedida e resultar em um tipo derivado de
Se exatamente uma das duas etapas acima resultar em um tipo derivado de System.Attribute, esse tipo será o resultado do attribute_name. Caso contrário, ocorrerá um erro em tempo de compilação.
Exemplo: se uma classe de atributo for encontrada com e sem esse sufixo, uma ambiguidade estará presente e ocorrerá um erro em tempo de compilação. Se o attribute_name for escrito de forma que seu identificador mais à direita seja um identificador literal (§6.4.3), somente um atributo sem sufixo será correspondido, permitindo que essa ambiguidade seja resolvida. O exemplo
[AttributeUsage(AttributeTargets.All)] public class Example : Attribute {} [AttributeUsage(AttributeTargets.All)] public class ExampleAttribute : Attribute {} [Example] // Error: ambiguity class Class1 {} [ExampleAttribute] // Refers to ExampleAttribute class Class2 {} [@Example] // Refers to Example class Class3 {} [@ExampleAttribute] // Refers to ExampleAttribute class Class4 {}mostra duas classes de atributo chamadas
ExampleeExampleAttribute. O atributo[Example]é ambíguo, pois pode se referir aExampleouExampleAttribute. O uso de um identificador literal permite que a intenção exata seja especificada em casos raros. O atributo[ExampleAttribute]não é ambíguo (embora seria se houvesse uma classe de atributo chamadaExampleAttributeAttribute!). Se a declaração de classeExamplefor removida, ambos os atributos se referirão à classe de atributo chamadaExampleAttribute, da seguinte maneira:[AttributeUsage(AttributeTargets.All)] public class ExampleAttribute : Attribute {} [Example] // Refers to ExampleAttribute class Class1 {} [ExampleAttribute] // Refers to ExampleAttribute class Class2 {} [@Example] // Error: no attribute named “Example” class Class3 {}fim do exemplo
É um erro em tempo de compilação usar uma classe de atributo de uso único mais de uma vez na mesma entidade.
Exemplo: o exemplo
[AttributeUsage(AttributeTargets.Class)] public class HelpStringAttribute : Attribute { public HelpStringAttribute(string value) { Value = value; } public string Value { get; } } [HelpString("Description of Class1")] [HelpString("Another description of Class1")] // multiple uses not allowed public class Class1 {}resulta em um erro de tempo de compilação porque ele tenta usar
HelpString, que é uma classe de atributo de uso único, mais de uma vez na declaração deClass1.fim do exemplo
Uma expressão E é um attribute_argument_expression se todas as instruções a seguir forem verdadeiras:
- O tipo de é um tipo de parâmetro de
Eatributo (§23.2.4). - Em tempo de compilação, o valor de
Epode ser resolvido para um dos seguintes:
Exemplo:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field)] public class TestAttribute : Attribute { public int P1 { get; set; } public Type P2 { get; set; } public object P3 { get; set; } } [Test(P1 = 1234, P3 = new int[]{1, 3, 5}, P2 = typeof(float))] class MyClass {} class C<T> { [Test(P2 = typeof(T))] // Error – T not a closed type. int x1; [Test(P2 = typeof(C<T>))] // Error – C<;T>; not a closed type. int x2; [Test(P2 = typeof(C<int>))] // Ok int x3; [Test(P2 = typeof(C<>))] // Ok int x4; }fim do exemplo
Os atributos de um tipo declarado em várias partes são determinados combinando, em uma ordem não especificada, os atributos de cada uma de suas partes. Se o mesmo atributo for colocado em várias partes, isso equivale a especificar esse atributo várias vezes no tipo.
Exemplo: as duas partes:
[Attr1, Attr2("hello")] partial class A {} [Attr3, Attr2("goodbye")] partial class A {}são equivalentes à seguinte declaração única:
[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")] class A {}fim do exemplo
Os atributos nos parâmetros de tipo são combinados da mesma maneira.
23.4 Instâncias de atributo
23.4.1 Geral
Uma instância de atributo é uma instância que representa um atributo em tempo de execução. Um atributo é definido com uma classe de atributo, argumentos posicionais e argumentos nomeados. Uma instância de atributo é uma instância da classe de atributo que é inicializada com os argumentos posicionais e nomeados.
A recuperação de uma instância de atributo envolve processamento em tempo de compilação e tempo de execução, conforme descrito nas subcláusulas a seguir.
23.4.2 Compilação de um atributo
A compilação de um atributo com a classe Tde atributo , positional_argument_listP, named_argument_listN e especificada em uma entidade E de programa é compilada em um assembly A por meio das seguintes etapas:
- Siga as etapas de processamento em tempo de compilação para compilar um object_creation_expression do novo formulário
T(P). Essas etapas resultam em um erro em tempo de compilação ou determinam um construtorCde instância queTpode ser invocado em tempo de execução. - Se
Cnão tiver acessibilidade pública, ocorrerá um erro em tempo de compilação. - Para cada named_argument
ArgemN:- Seja
Nameo identificador do named_argumentArg. -
Namedeve identificar um campo ou propriedade pública de leitura/gravação não estática emT. SeTnão tiver esse campo ou propriedade, ocorrerá um erro em tempo de compilação.
- Seja
- Se qualquer um dos valores dentro de positional_argument_list
Pou um dos valores dentro de named_argument_listNfor do tipoSystem.Stringe o valor não estiver bem formado conforme definido pelo Padrão Unicode, será definido se o valor compilado é igual ao valor de tempo de execução recuperado (§23.4.3).Observação: por exemplo, uma cadeia de caracteres que contém uma unidade de código UTF-16 substituto alto que não é imediatamente seguida por uma unidade de código substituto baixa não é bem formada. fim da observação
- Armazene as seguintes informações (para instanciação em tempo de execução do atributo) na saída do assembly pelo compilador como resultado da compilação do programa que contém o atributo: a classe
Tde atributo , o construtorCde instância emT, o positional_argument_listP, o named_argument_listNe a entidadeEde programa associada , com os valores resolvidos completamente em tempo de compilação.
23.4.3 Recuperação em tempo de execução de uma instância de atributo
O uso dos termos definidos em §23.4.2, a instância de atributo representada por T, Ce Pe Nassociada E pode ser recuperada em tempo de execução do assembly A usando as seguintes etapas:
- Siga as etapas de processamento em tempo de executar um object_creation_expression do formulário
new T(P), usando o construtor de instânciaCe valores conforme determinado no tempo de compilação. Essas etapas resultam em uma exceção ou produzem uma instânciaOdeT. - Para cada named_argument
ArgemN, na ordem:- Seja
Nameo identificador do named_argumentArg. SeNamenão identificar um campo ou propriedade pública de leitura/gravação não estático emO, uma exceção será lançada. - Seja
Valueo resultado da avaliação do attribute_argument_expression deArg. - Se
Nameidentificar um campo emO, defina esse campo comoValue. - Caso contrário, Nome identificará uma propriedade em
O. Defina essa propriedade como Valor. - O resultado é
O, uma instância da classeTde atributo que foi inicializada com o positional_argument_listPe o named_argument_listN.
- Seja
Observação: o formato para armazenar
T,C,P,N(e associá-lo aE) eAo mecanismo para especificarEe recuperarT,C,P,Nfrom (e, portanto, como uma instância deAatributo é obtida em tempo de execução) está além do escopo desta especificação. fim da observação
23.5 Atributos reservados
23.5.1 Geral
Vários atributos afetam a linguagem de alguma forma. Esses atributos incluem:
-
System.AttributeUsageAttribute(§23.5.2), que é usado para descrever as maneiras pelas quais uma classe de atributo pode ser usada. -
System.Diagnostics.ConditionalAttribute(§23.5.3), é uma classe de atributo de vários usos que é usada para definir métodos condicionais e classes de atributo condicional. Esse atributo indica uma condição testando um símbolo de compilação condicional. -
System.ObsoleteAttribute(§23.5.4), que é usado para marcar um membro como obsoleto. -
System.Runtime.CompilerServices.AsyncMethodBuilderAttribute(§23.5.5), que é usado para estabelecer um construtor de tarefas para um método assíncrono. -
System.Runtime.CompilerServices.CallerLineNumberAttribute(§23.5.6.2),System.Runtime.CompilerServices.CallerFilePathAttribute(§23.5.6.3) eSystem.Runtime.CompilerServices.CallerMemberNameAttribute(§23.5.6.4), que são usados para fornecer informações sobre o contexto de chamada para parâmetros opcionais. -
System.Runtime.CompilerServices.EnumeratorCancellationAttribute(§23.5.8), que é usado para especificar o parâmetro para o token de cancelamento em um iterador assíncrono.
Os atributos de análise estática anulável (§23.5.7) podem melhorar a correção dos avisos gerados para nullabilidades e estados nulos (§8.9.5).
Um ambiente de execução pode fornecer atributos adicionais definidos pela implementação que afetam a execução de um programa C#.
23.5.2 O atributo AttributeUsage
O atributo AttributeUsage é usado para descrever a maneira pela qual a classe de atributo pode ser usada.
Uma classe decorada com o AttributeUsage atributo deve derivar de System.Attribute, direta ou indiretamente. Do contrário, ocorrerá um erro em tempo de compilação.
Observação: para obter um exemplo de como usar esse atributo, consulte §23.2.2. fim da observação
23.5.3 O atributo condicional
23.5.3.1 Geral
O atributo Conditional habilita a definição de métodos condicionaise classe de atributo condicionales.
23.5.3.2 Métodos condicionais
Um método decorado com o atributo Conditional é um método condicional. Cada método condicional é, portanto, associado aos símbolos de compilação condicional declarados em seus atributo Conditional.
Exemplo:
class Eg { [Conditional("ALPHA")] [Conditional("BETA")] public static void M() { // ... } }declara
Eg.Mcomo um método condicional associado aos dois símbolos de compilação condicionalALPHAeBETA.fim do exemplo
Uma chamada para um método condicional será incluída se um ou mais de seus símbolos de compilação condicional associados forem definidos no ponto de chamada; caso contrário, a chamada será omitida.
Um método condicional está sujeito às seguintes restrições:
- O método condicional deve ser um método em um class_declaration ou struct_declaration. Um erro de tempo de compilação ocorrerá se o atributo
Conditionalfor especificado em um método em uma declaração de interface. - O método condicional não deve ser um acessador de uma propriedade, indexador ou evento.
- O método condicional deve ter um tipo de retorno de
void. - O método condicional não deve ser marcado com o modificador
override. No entanto, um método condicional pode ser marcado com o modificadorvirtual. As substituições de tal método são implicitamente condicionais e não devem ser explicitamente marcadas com um atributoConditional. - O método condicional não deve ser uma implementação de um método de interface. Do contrário, ocorrerá um erro em tempo de compilação.
- Os parâmetros do método condicional não devem ser parâmetros de saída.
Observação: atributos com um
AttributeUsage(§23.2.2) incluindoAttributeTargets.Methodnormalmente podem ser aplicados a acessadores de propriedades, indexadores e eventos. As restrições acima proíbem esse uso doConditionalatributo. fim da observação
Além disso, ocorrerá um erro em tempo de compilação se um delegado for criado a partir de um método condicional.
Exemplo: o exemplo
#define DEBUG using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public static void M() { Console.WriteLine("Executed Class1.M"); } } class Class2 { public static void Test() { Class1.M(); } }declara
Class1.Mcomo um método condicional.Class2o métodoTestchama esse método. Como o símbolo de compilação condicionalDEBUGé definido, seClass2.Testfor chamado, ele chamaráM. Se o símboloDEBUGnão tivesse sido definido, entãoClass2.Testnão chamariaClass1.M.fim do exemplo
É importante entender que a inclusão ou exclusão de uma chamada para um método condicional é controlada pelos símbolos de compilação condicional no ponto da chamada.
Exemplo: no código a seguir
// File Class1.cs: using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public static void F() { Console.WriteLine("Executed Class1.F"); } } // File Class2.cs: #define DEBUG class Class2 { public static void G() { Class1.F(); // F is called } } // File Class3.cs: #undef DEBUG class Class3 { public static void H() { Class1.F(); // F is not called } }As classes
Class2eClass3cada uma contém chamadas para o métodoClass1.Fcondicional , que é condicional com base em se está ou nãoDEBUGdefinido. Como esse símbolo é definido no contexto deClass2, mas não emClass3, a chamada paraFemClass2é incluída, enquanto a chamada paraFemClass3é omitida.fim do exemplo
O uso de métodos condicionais em uma cadeia de herança pode ser confuso. As chamadas feitas para um método condicional por meio de base, do formulário base.M, estão sujeitas às regras normais de chamada de método condicional.
Exemplo: no código a seguir
// File Class1.cs using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public virtual void M() => Console.WriteLine("Class1.M executed"); } // File Class2.cs class Class2 : Class1 { public override void M() { Console.WriteLine("Class2.M executed"); base.M(); // base.M is not called! } } // File Class3.cs #define DEBUG class Class3 { public static void Main() { Class2 c = new Class2(); c.M(); // M is called } }
Class2inclui uma chamada para oMdefinido em sua classe base. Essa chamada é omitida porque o método base é condicional com base na presença do símboloDEBUG, que é indefinido. Assim, o método grava apenas no console "Class2.M executed". O uso criterioso de pp_declarations pode eliminar esses problemas.fim do exemplo
23.5.3.3 Classes de atributo condicional
Uma classe de atributo (§23.2) decorada com um ou mais Conditional atributos é uma classe de atributo condicional. Uma classe de atributo condicional é, portanto, associado aos símbolos de compilação condicional declarados em seus atributo Conditional.
Exemplo:
[Conditional("ALPHA")] [Conditional("BETA")] public class TestAttribute : Attribute {}declara
TestAttributecomo uma classe de atributo condicional associada aos símbolosALPHAde compilações condicionais eBETA.fim do exemplo
As especificações de atributo (§23.3) de um atributo condicional serão incluídas se um ou mais de seus símbolos de compilação condicional associados estiverem definidos no ponto de especificação, caso contrário, a especificação do atributo será omitida.
É importante observar que a inclusão ou exclusão de uma especificação de atributo de uma classe de atributo condicional é controlada pelos símbolos de compilação condicional no ponto da especificação.
Exemplo: no exemplo
// File Test.cs: using System; using System.Diagnostics; [Conditional("DEBUG")] public class TestAttribute : Attribute {} // File Class1.cs: #define DEBUG [Test] // TestAttribute is specified class Class1 {} // File Class2.cs: #undef DEBUG [Test] // TestAttribute is not specified class Class2 {}as classes
Class1eClass2são decoradas com atributoTest, que é condicional com base em se está ou nãoDEBUGdefinido. Como esse símbolo é definido no contexto deClass1, mas nãoClass2, a especificação do atributo Test onClass1é incluída, enquanto aTestespecificação do atributo onClass2é omitida.fim do exemplo
23.5.4 O atributo Obsoleto
O atributo Obsolete é usado para marcar tipos e membros de tipos que não devem mais ser usados.
Se um programa usar um tipo ou membro decorado com o atributo Obsolete, um compilador emitirá um aviso ou um erro. Especificamente, um compilador emitirá um aviso se nenhum parâmetro de erro for fornecido ou se o parâmetro de erro for fornecido e tiver o valor false. Um compilador emitirá um erro se o parâmetro de erro for especificado e tiver o valor true.
Exemplo: no código a seguir
[Obsolete("This class is obsolete; use class B instead")] class A { public void F() {} } class B { public void F() {} } class Test { static void Main() { A a = new A(); // Warning a.F(); } }A classe
Aestá decorada com o atributoObsolete. Cada uso deAinMainresulta em um aviso que inclui a mensagem especificada: "Esta classe está obsoleta; use a classeBem vez disso".fim do exemplo
23.5.5 O atributo AsyncMethodBuilder
Esse atributo é descrito em §15.14.1.
23.5.6 Atributos de informações de chamador
23.5.6.1 Geral
Para fins como registro em log e relatórios, às vezes é útil para um membro da função obter determinadas informações em tempo de compilação sobre o código de chamada. Os atributos caller-info fornecem uma maneira de passar essas informações de forma transparente.
Quando um parâmetro opcional é anotado com um dos atributos caller-info, omitir o argumento correspondente em uma chamada não necessariamente faz com que o valor do parâmetro padrão seja substituído. Em vez disso, se as informações especificadas sobre o contexto de chamada estiverem disponíveis, essas informações serão passadas como o valor do argumento.
Exemplo:
public void Log( [CallerLineNumber] int line = -1, [CallerFilePath] string path = null, [CallerMemberName] string name = null ) { Console.WriteLine((line < 0) ? "No line" : "Line "+ line); Console.WriteLine((path == null) ? "No file path" : path); Console.WriteLine((name == null) ? "No member name" : name); }Uma chamada para
Log()sem argumentos imprimiria o número da linha e o caminho do arquivo da chamada, bem como o nome do membro no qual a chamada ocorreu.fim do exemplo
Os atributos Caller-info podem ocorrer em parâmetros opcionais em qualquer lugar, inclusive em declarações de delegado. No entanto, os atributos específicos caller-info têm restrições sobre os tipos dos parâmetros que podem atribuir, de modo que sempre haverá uma conversão implícita de um valor substituído para o tipo de parâmetro.
É um erro ter o mesmo atributo caller-info em um parâmetro tanto na parte de definição quanto na de implementação de uma declaração de método parcial. Somente os atributos caller-info na parte de definição são aplicados, enquanto os atributos caller-info que ocorrem apenas na parte de implementação são ignorados.
As informações do chamador não afetam a resolução de sobrecarga. Como os parâmetros opcionais atribuídos ainda são omitidos do código-fonte do chamador, a resolução de sobrecarga ignora esses parâmetros da mesma forma que ignora outros parâmetros opcionais omitidos (§12.6.4).
As informações do chamador só são substituídas quando uma função é invocada explicitamente no código-fonte. Invocações implícitas, como chamadas implícitas de construtor pai, não têm um local de origem e não substituirão as informações do chamador. Além disso, as chamadas associadas dinamicamente não substituirão as informações do chamador. Quando um parâmetro atribuído caller-info é omitido nesses casos, o valor padrão especificado do parâmetro é usado.
Uma exceção são as expressões de consulta. Essas são consideradas expansões sintáticas e, se as chamadas forem expandidas para omitir parâmetros opcionais com atributos caller-info, as informações do chamador serão substituídas. O local usado é o local da cláusula de consulta da qual a chamada foi gerada.
Se mais de um atributo caller-info for especificado em um determinado parâmetro, eles serão reconhecidos na seguinte ordem: CallerLineNumber, CallerFilePath, CallerMemberName. Considere a seguinte declaração de parâmetro:
[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...
CallerLineNumber tem precedência e os outros dois atributos são ignorados. Se CallerLineNumber fossem omitidos, CallerFilePath teriam precedência e CallerMemberName seriam ignorados. A ordenação lexical desses atributos é irrelevante.
23.5.6.2 O atributo CallerLineNumber
O atributo System.Runtime.CompilerServices.CallerLineNumberAttribute é permitido em parâmetros opcionais quando há uma conversão implícita padrão (§10.4.2) do valor int.MaxValue constante para o tipo do parâmetro. Isso garante que qualquer número de linha não negativo até esse valor possa ser passado sem erros.
Se uma invocação de função de um local no código-fonte omitir um parâmetro opcional com o CallerLineNumberAttribute, um literal numérico que representa o número de linha desse local será usado como um argumento para a invocação em vez do valor do parâmetro padrão.
Se a invocação abranger várias linhas, a linha escolhida será dependente da implementação.
O número da linha pode ser afetado por #line diretivas (§6.5.8).
23.5.6.3 O atributo CallerFilePath
O atributo System.Runtime.CompilerServices.CallerFilePathAttribute é permitido nos parâmetros opcionais quando há uma conversão implícita padrão (§10.4.2) do valor string constante para o tipo do parâmetro.
Se uma invocação de função de um local no código-fonte omitir um parâmetro opcional com o CallerFilePathAttribute, um literal de cadeia de caracteres que representa o caminho do arquivo desse local é usado como argumento para a invocação em vez do valor padrão do parâmetro.
O formato do caminho do arquivo depende da implementação.
O caminho do arquivo pode ser afetado por #line diretivas (§6.5.8).
23.5.6.4 O atributo CallerMemberName
O atributo System.Runtime.CompilerServices.CallerMemberNameAttribute é permitido nos parâmetros opcionais quando há uma conversão implícita padrão (§10.4.2) do valor string constante para o tipo do parâmetro.
Se uma invocação de função de um local dentro do corpo de um membro de função ou dentro de um atributo aplicado ao próprio membro da função ou seu tipo de retorno, parâmetros ou parâmetros de tipo no código-fonte omitir um parâmetro opcional com o CallerMemberNameAttribute, um literal de cadeia de caracteres que representa o nome desse membro será usado como um argumento para a invocação em vez do valor do parâmetro padrão.
Para invocações que ocorrem em métodos genéricos, somente o próprio nome do método é usado, sem a lista de parâmetros de tipo.
Para invocações que ocorrem em implementações explícitas de membros de interface, somente o próprio nome do método é usado, sem a qualificação de interface anterior.
Para invocações que ocorrem em acessadores de propriedade ou evento, o nome do membro usado é o da própria propriedade ou evento.
Para invocações que ocorrem nos acessadores do indexador, o nome do membro usado é fornecido por um IndexerNameAttribute (§23.6) no membro do indexador, se presente, ou o nome Item padrão de outra forma.
Para invocações que ocorrem em inicializadores de campo ou evento, o nome do membro usado é o nome do campo ou evento que está sendo inicializado.
Para invocações que ocorrem em declarações de construtores de instância, construtores estáticos, finalizadores e operadores, o nome do membro usado depende da implementação.
23.5.7 Atributos de análise de código
23.5.7.1 Geral
Os atributos nessa subcláusula são usados para fornecer informações adicionais para oferecer suporte a um compilador que fornece nulidade e diagnóstico de estado nulo (§8.9.5). Não é necessário que o compilador execute nenhum diagnóstico de estado nulo. A presença ou ausência desses atributos não afeta a linguagem nem o comportamento de um programa. Um compilador que não fornece diagnóstico de estado nulo deve ler e ignorar a presença desses atributos. Um compilador que fornece diagnóstico de estado nulo deve usar o significado definido neste subclauso para qualquer um desses atributos que ele usa para informar seu diagnóstico.
Os atributos de análise de código são declarados no namespace System.Diagnostics.CodeAnalysis.
| Atributo | Significado |
|---|---|
AllowNull (§23.5.7.2) |
Um argumento não anulável pode ser nulo. |
DisallowNull (§23.5.7.3) |
Um argumento anulável nunca deve ser nulo. |
MaybeNull (§23.5.7.6) |
Um valor retornado não anulável pode ser nulo. |
NotNull (§23.5.7.8) |
Um valor retornado anulável nunca será nulo. |
MaybeNullWhen (§23.5.7.7) |
Um argumento não anulável pode ser nulo quando o método retorna o valor bool especificado. |
NotNullWhen (§23.5.7.10) |
Um argumento anulável não poderá ser nulo quando o método retorna o valor bool especificado. |
NotNullIfNotNull (§23.5.7.9) |
Um valor de retorno não será nulo se o argumento do parâmetro especificado não for nulo. |
DoesNotReturn (§23.5.7.4) |
Esse método nunca retorna. |
DoesNotReturnIf (§23.5.7.5) |
Esse método nunca retornará se o parâmetro associado bool tiver o valor especificado. |
As subclausas a seguir em §23.5.7.1 são condicionalmente normativas.
23.5.7.2 O atributo AllowNull
Especifica que um valor nulo é permitido como entrada, mesmo que o tipo correspondente não o permita
Exemplo: considere a propriedade de leitura/gravação que nunca retorna
nullporque tem um valor padrão razoável. No entanto, um usuário pode fornecer null ao acessor set para definir a propriedade com esse valor padrão.#nullable enable public class X { [AllowNull] public string ScreenName { get => _screenName; set => _screenName = value ?? GenerateRandomScreenName(); } private string _screenName = GenerateRandomScreenName(); private static string GenerateRandomScreenName() => ...; }Dado o seguinte uso do acessador set dessa propriedade
var v = new X(); v.ScreenName = null; // may warn without attribute AllowNullsem o atributo, um compilador pode gerar um aviso porque a propriedade do tipo não anulável parece ser definida como um valor nulo. A presença do atributo suprime esse aviso. fim do exemplo
23.5.7.3 O atributo DisallowNull
Especifica que um valor nulo não é permitido como entrada, mesmo que o tipo correspondente o permita.
Exemplo: considere a propriedade a seguir na qual null é o valor padrão, mas os clientes só podem defini-lo como um valor não nulo.
#nullable enable public class X { [DisallowNull] public string? ReviewComment { get => _comment; set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null"); } private string? _comment = default; }O acessador get pode retornar o valor padrão de
null, portanto, um compilador pode avisar que ele deve ser verificado antes do acesso. Além disso, ele avisa aos chamadores que, embora possa ser nulo, eles não devem defini-lo explicitamente como nulo. fim do exemplo
23.5.7.4 O atributo DoesNotReturn
Especifica que um determinado método nunca retorna.
Exemplo: considere o seguinte exemplo:
public class X { [DoesNotReturn] private void FailFast() => throw new InvalidOperationException(); public void SetState(object? containedField) { if ((!isInitialized) || (containedField == null)) { FailFast(); } // null check not needed. _field = containedField; } private bool isInitialized = false; private object _field; }A presença do atributo ajuda um compilador de várias maneiras. Primeiro, um compilador poderá emitir um aviso se houver um caminho em que o método possa sair sem gerar uma exceção. Em segundo lugar, um compilador pode suprimir avisos anuláveis em qualquer código após uma chamada para esse método, até que uma cláusula catch apropriada seja encontrada. Terceiro, o código inacessível não afetará nenhum estado nulo.
O atributo não altera a análise de acessibilidade (§13.2) ou atribuição definida (§9.4) com base na presença desse atributo. Ele é usado apenas para afetar avisos de nulidade. fim do exemplo
23.5.7.5 O atributo DoesNotReturnIf
Especifica que um determinado método nunca retorna se o parâmetro associado bool tiver o valor especificado.
Exemplo: considere o seguinte exemplo:
#nullable enable public class X { private void ThrowIfNull([DoesNotReturnIf(true)] bool isNull, string argumentName) { if (!isNull) { throw new ArgumentException(argumentName, $"argument {argumentName} can't be null"); } } public void SetFieldState(object containedField) { ThrowIfNull(containedField == null, nameof(containedField)); // unreachable code when "isInitialized" is false: _field = containedField; } private bool isInitialized = false; private object _field = default!; }fim do exemplo
23.5.7.6 O atributo MaybeNull
Especifica que um valor retornado não anulável pode ser nulo.
Exemplo: considere o seguinte método genérico:
#nullable enable public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }A ideia desse código é que se
Tfor substituído porstring,T?torna-se uma anotação anulável. No entanto, esse código não é legal porqueTnão está restrito a ser um tipo de referência. No entanto, adicionar este atributo resolve o problema:#nullable enable [return: MaybeNull] public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }O atributo informa aos chamadores que o contrato implica um tipo não anulável, mas o valor retornado pode realmente ser
null. fim do exemplo
23.5.7.7 O atributo MaybeNullWhen
Especifica que um argumento não nulo pode ser null quando o método retorna o valor bool especificado. Isso é semelhante ao MaybeNull atributo (§23.5.7.6), mas inclui um parâmetro para o valor retornado especificado.
23.5.7.8 O atributo NotNull
Especifica que um valor anulável nunca será null se o método retornar (em vez de gerar).
Exemplo: considere o seguinte exemplo:
#nullable enable public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "") => _ = value ?? throw new ArgumentNullException(valueExpression); public static void LogMessage(string? message) { ThrowWhenNull(message, nameof(message)); Console.WriteLine(message.Length); }Quando tipos de referência anuláveis são habilitados, o método
ThrowWhenNullé compilado sem avisos. Quando esse método retorna, o argumentovaluetem a garantia de não sernull. Entretanto, é aceitável chamarThrowWhenNullcom uma referência nula. fim do exemplo
23.5.7.9 O atributo NotNullIfNotNull
Especifica que um valor retornado não null se o argumento para o parâmetro especificado não for null.
Exemplo: o estado nulo de um valor retornado dependeria do estado nulo de um ou mais argumentos. Para auxiliar a análise de um compilador quando um método sempre retorna um valor não nulo quando determinados argumentos não são
nullo atributoNotNullIfNotNullpode ser usado. Considere o método a seguir:#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }Se o argumento
urlnão fornull,nullnão será retornado. Quando as referências anuláveis estão habilitadas, essa assinatura funciona corretamente, desde que a API nunca aceite um argumento nulo. No entanto, se o argumento puder ser nulo, o valor retornado também poderá ser nulo. Para expressar esse contrato corretamente, anote este método da seguinte maneira:#nullable enable [return: NotNullIfNotNull("url")] string? GetTopLevelDomainFromFullUrl(string? url) { ... }fim do exemplo
23.5.7.10 O atributo NotNullWhen
Especifica que um argumento não nulo poderá ser null quando o método retorna o valor bool especificado.
Exemplo: o método
String.IsNullOrEmpty(String)library retornatruequando o argumento énullou uma cadeia de caracteres vazia. É uma forma de verificação de nulidade: Quem chama não precisa verificar se o argumento é nulo se o método retornarfalse. Para tornar um método como esse ciente de nulidade, transforme o tipo de parâmetro em um tipo de referência anulável e adicione o atributo NotNullWhen:#nullable enable bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }fim do exemplo
23.5.8 O atributo EnumeratorCancellation
Especifica o parâmetro que representa o CancellationToken iterador assíncrono (§15.15). O argumento para esse parâmetro deve ser combinado com o argumento passado para IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken). Esse token combinado deve ser sondado por IAsyncEnumerator<T>.MoveNextAsync() (§15.15.5.2). Os tokens devem ser combinados em um único token como se fossem por CancellationToken.CreateLinkedTokenSource e sua Token propriedade. O token combinado será cancelado se um dos dois tokens de origem for cancelado. O token combinado é visto como o argumento para o método iterador assíncrono (§15.15) no corpo desse método.
Será um erro se o System.Runtime.CompilerServices.EnumeratorCancellation atributo for aplicado a mais de um parâmetro. O compilador poderá produzir um aviso se:
- O
EnumeratorCancellationatributo é aplicado a um parâmetro de um tipo diferenteCancellationTokende , - ou se o
EnumeratorCancellationatributo for aplicado a um parâmetro em um método que não seja um iterador assíncrono (§15.15), - ou se o
EnumeratorCancellationatributo for aplicado a um parâmetro em um método que retorna uma interface enumerável assíncrona (§15.15.3) em vez de uma interface de enumerador assíncrona (§15.15.2).
O iterador não terá acesso ao CancellationToken argumento para GetAsyncEnumerator quando nenhum atributo tiver esse parâmetro.
Exemplo: o método
GetStringsAsync()é um iterador assíncrono. Antes de fazer qualquer trabalho para recuperar o próximo valor, ele verifica o token de cancelamento para determinar se a iteração deve ser cancelada. Se o cancelamento for solicitado, nenhuma ação adicional será tomada.public static async Task ExampleCombination() { var sourceOne = new CancellationTokenSource(); var sourceTwo = new CancellationTokenSource(); await using (IAsyncEnumerator<string> enumerator = GetStringsAsync(sourceOne.Token).GetAsyncEnumerator(sourceTwo.Token)) { while (await enumerator.MoveNextAsync()) { string number = enumerator.Current; if (number == "8") sourceOne.Cancel(); if (number == "5") sourceTwo.Cancel(); Console.WriteLine(number); } } } static async IAsyncEnumerable<string> GetStringsAsync( [EnumeratorCancellation] CancellationToken token) { for (int i = 0; i < 10; i++) { if (token.IsCancellationRequested) yield break; await Task.Delay(1000, token); yield return i.ToString(); } }fim do exemplo
23.6 Atributos para interoperação
Para interoperação com outras linguagens, um indexador pode ser implementado usando propriedades indexadas. Se nenhum atributo IndexerName estiver presente para um indexador, o nome Item será usado por padrão. O atributo IndexerName permite que um desenvolvedor substitua esse padrão e especifique um nome diferente.
Exemplo: por padrão, o nome de um indexador é
Item. Isso pode ser substituído da seguinte forma:[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { get { ... } set { ... } }Agora, o nome do indexador é
TheItem.fim do exemplo
ECMA C# draft specification