Partilhar via


22 Atributos

22.1 Generalidades

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 atributos. Os programadores podem então 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 HelpAttribute atributo que 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. Nota final

Os atributos são definidos através da declaração de classes de atributos (§22.2), que podem ter parâmetros posicionais e nomeados (§22.2.3). Os atributos são anexados a entidades em um programa C# usando especificações de atributo (§22.3) e podem ser recuperados em tempo de execução como instâncias de atributo (§22.4).

22.2 Classes de atributos

22.2.1 Generalidades

Uma classe que deriva da classe System.Attributeabstrata , 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 de programa. Por convenção, as classes de atributos são nomeadas com um sufixo de Attribute. Os usos de um atributo podem incluir ou omitir esse sufixo.

Uma declaração de classificação genérica não pode 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 attribute

Exemplo final

22.2.2 Utilização de atributos

O atributo AttributeUsage (§22.5.2) é usado para descrever como uma classe de atributo pode ser usada.

AttributeUsage tem um parâmetro posicional (§22.2.3) que permite que uma classe de atributo especifique os tipos de entidades de programa nos quais ela pode ser usada.

Exemplo: O exemplo a seguir define uma classe de atributo nomeada SimpleAttribute que pode ser colocada somente em class_declarations e interface_declarations e mostra vários usos do Simple atributo.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class SimpleAttribute : Attribute
{ 
    ... 
}

[Simple] class Class1 {...}
[Simple] interface Interface1 {...}

Embora este atributo seja definido com o nome SimpleAttribute, quando este atributo é usado, o sufixo Attribute pode ser omitido, resultando no nome Simplecurto . Assim, o exemplo acima é semanticamente equivalente ao seguinte

[SimpleAttribute] class Class1 {...}
[SimpleAttribute] interface Interface1 {...}

Exemplo final

AttributeUsage tem um parâmetro nomeado (§22.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 de uso múltiplo e poderá ser especificada mais de uma vez em uma entidade. Se AllowMultiple 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 AuthorAttribute e mostra uma declaração de classe com dois usos do Author atributo:

[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 
{
    ...
}

Exemplo final

AttributeUsage tem outro parâmetro nomeado (§22.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 é falso, então esse atributo não é herdado. Se não for especificado, seu valor padrão será true.

Uma classe X de atributo que não tem um AttributeUsage atributo anexado a ela, como em

class X : Attribute { ... }

é equivalente ao seguinte:

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

22.2.3 Parâmetros posicionais e nomeados

As classes de atributos podem ter parâmetrosposicionais s e parâmetrosnomeados. 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 público de leitura/gravação não estático e propriedade 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 de conjunto público.

Exemplo: O exemplo a seguir define uma classe de atributo chamada HelpAttribute que tem um parâmetro posicional, urle um parâmetro nomeado, Topic. Embora não seja estática e pública, a propriedade Url não define um parâmetro nomeado, uma vez que 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
{
}

Exemplo final

22.2.4 Tipos de parâmetros de atributo

Os tipos de parâmetros posicionais e nomeados para uma classe de atributo são limitados aos tipos de parâmetros de atributo, que são:

  • Um dos seguintes tipos: bool, byte, , char, doublefloat, int, long, sbyte, short, stringuintulongushort.
  • O tipo object.
  • O tipo System.Type.
  • Tipos de enum.
  • 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.

22.3 Especificação de atributos

Especificação de atributo é a aplicação de um atributo previamente definido a uma entidade de programa. Um atributo é uma parte de informações declarativas adicionais que é especificada para uma entidade de programa. Os atributos podem ser especificados no escopo global (para especificar atributos no conjunto ou módulo que contém) e para type_declaration s (§14.7), class_member_declarations (§15.3), interface_member_declarations (§18.4), struct_member_declarations (§16.3), enum_member_declarations (§19.2), accessor_declarations (§15.7.3), event_accessor_ declaraçõess (§15.8), elementos de parameter_lists (§15.6.2) e elementos de type_parameter_lists (§15.2.3).

Os atributos são especificados em seções de atributos. Uma seção de atributos consiste em um par de colchetes, que cercam uma lista separada por vírgulas de um ou mais atributos. A ordem em que os atributos são especificados em tal lista e a ordem em que as seções anexadas à mesma entidade do programa são organizadas não é significativa. Por exemplo, as especificações [A][B]de atributo , [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 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_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 o global_attribute_target de produção, e no texto abaixo, o identificador deve ter uma grafia igual a ou assembly, onde igualdade é a definida no module. Para a produção attribute_target, e no texto abaixo, identificador deve ter uma grafia que não é igual a assembly ou module, usando a mesma definição de igualdade como 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 que a atribuição simples. A ordem dos argumentos nomeados não é significativa.

Nota: Por conveniência, é permitida uma vírgula à direita numa global_attribute_section e numa attribute_section, tal como é permitida numa array_initializer (§17.7). Nota final

O attribute_name identifica uma classe de atributo.

Quando um atributo é colocado no nível global, uma global_attribute_target_specifier é necessária. Quando o global_attribute_target for igual a:

  • assembly — o alvo é o conjunto de contenção
  • module — o alvo é o módulo de contenção

Não são permitidos outros valores para global_attribute_target .

Os nomes attribute_target padronizados typevar Estas denominações-alvo só podem ser utilizadas nos seguintes contextos:

  • event — um acontecimento.
  • field — um campo. Um evento semelhante a um campo (ou seja, um evento 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, propriedade obter e definir acessadores, indexador obter e definir acessadores e evento adicionar e remover acessadores. 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, adicionar e remover acessadores 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, propriedade obter acessador e indexador obter acessador.
  • type — um delegado, classe, struct, enum e interface.
  • typevar — um parâmetro de tipo.

Certos 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 for igual a:
    • type — o alvo é o delegado
    • return — o alvo é 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 for igual a:
    • method — o objetivo é o método
    • return — o alvo é 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 for igual a:
    • method — o alvo é o operador
    • return — o alvo é o valor de retorno
  • Para um atributo em uma declaração get accessor para uma declaração de propriedade ou indexador, o destino padrão é o método associado. Caso contrário, quando o attribute_target for igual a:
    • method — o objetivo é o método associado
    • return — o alvo é o valor de retorno
  • Para um atributo especificado em um acessador definido, para uma declaração de propriedade ou indexador, o destino padrão é o método associado. Caso contrário, quando o attribute_target for igual a:
    • method — o objetivo é o método associado
    • param — o alvo é 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 for igual a:
    • field — o destino é o campo de suporte gerado pelo compilador para a propriedade
  • Para um atributo especificado em uma declaração de evento que omite event_accessor_declarations o destino padrão é a declaração de evento. Caso contrário, quando o attribute_target for igual a:
    • event — o alvo é a declaração do evento
    • field — o alvo é o campo
    • method — os objetivos 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 objetivo é o método associado
    • param — o alvo é o parâmetro solitário

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 {}

exemplo final.

Uma implementação pode aceitar outros attribute_targets, cujos objetivos são de implementação definidos. Uma implementação que não reconheça tal attribute_target deve emitir um aviso e ignorar o attribute_section contido.

Por convenção, as classes de atributos são nomeadas com um sufixo de Attribute. Um attribute_name pode incluir ou omitir esse sufixo. Especificamente, uma attribute_name é resolvida da seguinte forma:

  • Se o identificador mais à direita do attribute_name for um identificador literal (§6.4.3), então o attribute_name é resolvido como um type_name (§7.8). Se o resultado não for um tipo derivado de , ocorrerá um erro em tempo de System.Attributecompilação.
  • Caso contrário,
    • O attribute_name é resolvido como um type_name (§7.8), exceto quaisquer erros são suprimidos. Se essa resolução for bem-sucedida e resultar em um tipo derivado System.Attribute , o tipo será o resultado desta etapa.
    • Os caracteres Attribute são acrescentados ao identificador mais à direita no attribute_name e a cadeia de tokens resultante é resolvida como um type_name (§7.8), exceto quaisquer erros são suprimidos. Se essa resolução for bem-sucedida e resultar em um tipo derivado System.Attribute , o tipo será o resultado desta etapa.

Se exatamente uma das duas etapas acima resultar em um tipo derivado de System.Attribute, então esse tipo é 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 um erro em tempo de compilação resultará. Se o attribute_name estiver escrito de tal forma que o seu identificador mais à direita seja um identificador literal (§6.4.3), apenas um atributo sem sufixo é correspondido, permitindo assim resolver tal ambiguidade. 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 atributos denominadas Example e ExampleAttribute. O atributo [Example] é ambíguo, uma vez que pode referir-se a qualquer um Example ou ExampleAttribute. O uso de um identificador literal permite que a intenção exata seja especificada em casos tão raros. O atributo [ExampleAttribute] não é ambíguo (embora fosse se houvesse uma classe de atributo chamada ExampleAttributeAttribute!). Se a declaração para a classe Example for removida, ambos os atributos se referem à classe de atributo chamada ExampleAttribute, 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 {}

Exemplo final

É 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 em tempo de compilação porque ele tenta usar HelpString, que é uma classe de atributo de uso único, mais de uma vez na declaração de Class1.

Exemplo final

Uma expressão E é um attribute_argument_expression se todas as seguintes instruções forem verdadeiras:

  • O tipo de é um tipo de parâmetro de E atributo (§22.2.4).
  • Em tempo de compilação, o valor de E pode ser resolvido para um dos seguintes:
    • Um valor constante.
    • Um System.Type objeto obtido usando um typeof_expression (§12.8.18) especificando um tipo não genérico, um tipo construído fechado (§8.4.3) ou um tipo genérico não acoplado (§8.4.4), mas não um tipo aberto (§8.4.3).
    • Uma matriz unidimensional de attribute_argument_expressions.

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;
}

Exemplo final

Os atributos de um tipo declarado em várias partes são determinados pela combinação, em uma ordem não especificada, dos atributos de cada uma de suas partes. Se o mesmo atributo for colocado em várias partes, é equivalente 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 {}

Exemplo final

Os atributos nos parâmetros de tipo combinam-se da mesma maneira.

22.4 Instâncias de atributos

22.4.1 Generalidades

Uma ocorrê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 ocorrê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 em tempo de execução, conforme descrito nas subcláusulas a seguir.

22.4.2 Compilação de um atributo

A compilação de um A das seguintes etapas:

  • Siga as etapas de processamento em tempo de compilação para compilar uma object_creation_expression do novo formulário T(P). Essas etapas resultam em um erro em tempo de compilação ou determinam um construtor C de instância que T pode ser invocado em tempo de execução.
  • Se C não tiver acessibilidade pública, ocorrerá um erro em tempo de compilação.
  • Para cada named_argumentArg em N:
    • Seja Name o identificador do named_argumentArg.
    • Name deve identificar um campo público de leitura/escrita não estático ou uma propriedade em T. Se T não tiver esse campo ou propriedade, ocorrerá um erro em tempo de compilação.
  • Se qualquer um dos valores dentro de positional_argument_listP ou um dos valores dentro de named_argument_listN for do tipo System.String e o valor não estiver bem formado conforme definido pelo Padrão Unicode, será definido pela implementação se o valor compilado é igual ao valor de tempo de execução recuperado (§22.4.3).

    Nota: Por exemplo, uma cadeia de caracteres que contém uma unidade de código UTF-16 substituta alta que não é imediatamente seguida por uma unidade de código substituto baixa não está bem formada. Nota final

  • 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 construtor C de instância em T, o positional_argument_listP, o named_argument_listN e a entidade Ede programa associada , com os valores resolvidos completamente em tempo de compilação.

22.4.3 Recuperação em tempo de execução de uma instância de atributo

Usando os termos definidos em §22.4.2, a instância de atributo representada por T, C, Pe , e Nassociada a E pode ser recuperada em tempo de execução do assembly A usando as seguintes etapas:

  • Siga as etapas de processamento em tempo de execução para executar um C de compilação. Essas etapas resultam em uma exceção ou produzem uma instância O de T.
  • Para cada named_argumentArg em N, pela ordem:
    • Seja Name o identificador do named_argumentArg. Se Name não identificar um campo público de leitura/gravação não estático ou uma propriedade no O, uma exceção será lançada.
    • Sejamos Value o resultado da avaliação do attribute_argument_expression de Arg.
    • Se Name identificar um campo no O, defina este campo como Value.
    • Caso contrário, Name identifica uma propriedade em O. Defina esta propriedade como Value.
    • O resultado é O, uma instância da classe T de atributo que foi inicializada com o positional_argument_listP e o named_argument_listN.

Nota: O formato para armazenar T, C, P, N (e associá-lo a E) e A o mecanismo para especificar E e recuperar T, C, P, de N (e, portanto, como uma instância de atributo é obtida em tempo de A execução) está além do escopo desta especificação. Nota final

Exemplo: Em uma implementação da CLI, as Help instâncias de atributo no assembly criadas compilando o programa de exemplo no §22.2.3 podem ser recuperadas com o seguinte programa:

public sealed class InterrogateHelpUrls
{
    public static void Main(string[] args)
    {
        Type helpType = typeof(HelpAttribute);
        string assemblyName = args[0];
        foreach (Type t in Assembly.Load(assemblyName).GetTypes()) 
        {
            Console.WriteLine($"Type : {t}");
            var attributes = t.GetCustomAttributes(helpType, false);
            var helpers = (HelpAttribute[]) attributes;
            foreach (var helper in helpers)
            {
                Console.WriteLine($"\tUrl : {helper.Url}");
            }
        }
    }
}

Exemplo final

22.5 Atributos reservados

22.5.1 Generalidades

Vários atributos afetam a linguagem de alguma forma. Estes atributos incluem:

  • System.AttributeUsageAttribute (§22.5.2), que é usado para descrever as maneiras pelas quais uma classe de atributo pode ser usada.
  • System.Diagnostics.ConditionalAttribute (§22.5.3), é uma classe de atributo multiuso que é usada para definir métodos condicionais e classes de atributos condicionais. Este atributo indica uma condição testando um símbolo de compilação condicional.
  • System.ObsoleteAttribute (§22.5.4), que é utilizado para marcar um membro como obsoleto.
  • System.Runtime.CompilerServices.AsyncMethodBuilderAttribute (§22.5.5), que é usado para estabelecer um construtor de tarefas para um método assíncrono.
  • System.Runtime.CompilerServices.CallerLineNumberAttribute (§22.5.6.2), System.Runtime.CompilerServices.CallerFilePathAttribute (§22.5.6.3) e System.Runtime.CompilerServices.CallerMemberNameAttribute (§22.5.6.4), que são usados para fornecer informações sobre o contexto de chamada para parâmetros opcionais.

Os atributos de análise estática anulável (§22.5.7) podem melhorar a correção dos avisos gerados para anulabilidades 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 em C#.

22.5.2 O atributo AttributeUsage

O atributo AttributeUsage é usado para descrever a maneira como a classe de atributo pode ser usada.

Uma classe decorada com o AttributeUsage atributo deve derivar de System.Attribute, direta ou indiretamente. Caso contrário, ocorrerá um erro em tempo de compilação.

Nota: Para um exemplo de utilização deste atributo, ver §22.2.2. Nota final

22.5.3 O atributo Condicional

22.5.3.1 Generalidades

O atributo Conditional permite a definição de métodos condicionais e classes de atributos condicionais.

22.5.3.2 Métodos condicionais

Um método decorado com o Conditional atributo é um método condicional. Cada método condicional é, portanto, associado com os símbolos de compilação condicional declarados em seus Conditional atributos.

Exemplo:

class Eg
{
    [Conditional("ALPHA")]
    [Conditional("BETA")]
    public static void M()
    {
        // ...
    }
}

declara Eg.M como um método condicional associado aos dois símbolos ALPHA de compilação condicional e BETA.

Exemplo final

Uma chamada para um método condicional é 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 de class_declaration ou struct_declaration. Um erro em tempo de compilação ocorre se o Conditional atributo é especificado em um método em uma declaração de interface.
  • O método condicional deve ter um tipo de retorno de void.
  • O método condicional não deve ser marcado com o override modificador. No entanto, um método condicional pode ser marcado com o virtual modificador. As substituições de tal método são implicitamente condicionais e não devem ser explicitamente marcadas com um Conditional atributo.
  • O método condicional não deve ser uma aplicação de um método de interface. Caso 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.

Além disso, um erro em tempo de compilação ocorre se um delegado é 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.M como um método condicional. Class2O método 's Test chama esse método. Uma vez que o símbolo DEBUG de compilação condicional é definido, se Class2.Test for chamado, ele chamará M. Se o símbolo DEBUG não tivesse sido definido, então Class2.Test não chamaria Class1.M.

Exemplo final

É 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 Class2 e Class3 cada uma contêm chamadas para o método Class1.Fcondicional , que é condicional com base na definição ou não DEBUG . Uma vez que este símbolo é definido no contexto de Class2 , mas não Class3, a chamada para F dentro Class2 é incluída, enquanto a chamada para F dentro Class3 é omitida.

Exemplo final

O uso de métodos condicionais em uma cadeia de herança pode ser confuso. As chamadas feitas para um método condicional através basede , 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
    }
}

Class2 inclui uma chamada para o M definido em sua classe base. Esta chamada é omitida porque o método base é condicional com base na presença do símbolo DEBUG, que é indefinido. Assim, o método grava apenas no console "Class2.M executed". O uso judicioso de pp_declarations pode eliminar tais problemas.

Exemplo final

22.5.3.3 Classes de atributos condicionais

Uma classe de atributo (§22.2) decorada com um ou mais Conditional atributos é uma classe de atributo condicional. Uma classe de atributo condicional é, portanto, associada aos símbolos de compilação condicional declarados em seus Conditional atributos.

Exemplo:

[Conditional("ALPHA")]
[Conditional("BETA")]
public class TestAttribute : Attribute {}

declara TestAttribute como uma classe de atributo condicional associada aos símbolos ALPHA de compilações condicionais e BETA.

Exemplo final

As especificações de atributo (§22.3) de um atributo condicional são incluídas se um ou mais dos seus símbolos de compilação condicional associados forem definidos no ponto de especificação, caso contrário, a especificação do atributo será omitida.

É importante notar 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 Class1 e Class2 são cada uma decorada com atributo Test, que é condicional com base em se é definido ou não DEBUG . Uma vez que este símbolo é definido no contexto de Class1 , mas não Class2, a especificação do atributo Test on Class1 é incluída, enquanto a especificação do Test atributo on Class2 é omitida.

Exemplo final

22.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 usa um tipo ou membro que é decorado com o atributo Obsolete, um compilador deve emitir um aviso ou um erro. Especificamente, um compilador deve 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 deve emitir um erro se o parâmetro error 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 A é decorada com o Obsolete atributo. Cada uso de A in Main resulta em um aviso que inclui a mensagem especificada, "Esta classe está obsoleta; use a classe B em vez disso".

Exemplo final

22.5.5 O atributo AsyncMethodBuilder

Este atributo é descrito no ponto 15.15.1.

22.5.6 Atributos de informações do chamador

22.5.6.1 Generalidades

Para fins como registro em log e relatórios, às vezes é útil para um membro da função obter certas 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 faz necessariamente 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.

Exemplo final

Os atributos calller-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 da parte definidora e implementadora de uma declaração de método parcial. Somente os atributos caller-info na parte definidora 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 da 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 é explicitamente invocada no código-fonte. Invocações implícitas, como chamadas implícitas do 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 vinculadas dinamicamente não substituirão as informações do chamador. Quando um parâmetro caller-info atribuído é omitido nesses casos, o valor padrão especificado do parâmetro é usado em vez disso.

Uma exceção são as expressões de consulta. Estas 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 a partir 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 fosse omitido, CallerFilePath teria precedência, e CallerMemberName seria ignorado. A ordenação lexical desses atributos é irrelevante.

22.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, então um literal numérico representando 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 se estender por várias linhas, a linha escolhida dependerá da implementação.

O número da linha pode ser afetado pelas #line diretivas (§6.5.8).

22.5.6.3 O atributo CallerFilePath

O atributo System.Runtime.CompilerServices.CallerFilePathAttribute é permitido em parâmetros opcionais quando há uma conversão implícita padrão (§10.4.2) do tipo do string parâmetro.

Se uma invocação de função de um local no código-fonte omitir um parâmetro opcional com o CallerFilePathAttribute, então um literal de cadeia de caracteres representando o caminho do arquivo desse local será usado como um argumento para a invocação em vez do valor do parâmetro padrão.

O formato do caminho do arquivo depende da implementação.

O caminho do arquivo pode ser afetado por #line diretivas (§6.5.8).

22.5.6.4 O atributo CallerMemberName

O atributo System.Runtime.CompilerServices.CallerMemberNameAttribute é permitido em parâmetros opcionais quando há uma conversão implícita padrão (§10.4.2) do tipo do string parâmetro.

Se uma invocação de função de um local dentro do corpo de um membro da 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 omite um parâmetro opcional com o CallerMemberNameAttribute, então um literal de cadeia de caracteres representando o nome desse membro é usado como um argumento para a invocação em vez do valor do parâmetro padrão.

Para invocações que ocorrem dentro de métodos genéricos, apenas o próprio nome do método é usado, sem a lista de parâmetros type.

Para invocações que ocorrem em implementações explícitas de membro da interface, somente o nome do método em si é usado, sem a qualificação de interface anterior.

Para invocações que ocorrem dentro de acessadores de propriedade ou evento, o nome do membro usado é o da propriedade ou evento em si.

Para invocações que ocorrem dentro de acessadores indexadores, o nome de membro usado é aquele fornecido por um IndexerNameAttribute (§22.6) no membro indexador, se presente, ou o nome Item padrão de outra forma.

Para invocações que ocorrem dentro de inicializadores de campo ou evento, o nome de 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 de membro usado depende da implementação.

22.5.7 Atributos de análise de código

22.5.7.1 Generalidades

Os atributos nesta seção são usados para fornecer informações adicionais para dar suporte a um compilador que fornece diagnóstico de anulabilidade e estado nulo (§8.9.5). Um compilador não é necessário para executar nenhum diagnóstico de estado nulo. A presença ou ausência desses atributos não afetam a linguagem nem o comportamento de um programa. Um compilador que não fornece diagnósticos de estado nulo deve ler e ignorar a presença desses atributos. Um compilador que fornece diagnósticos de estado nulo deve usar o significado definido nesta seção para qualquer um desses atributos que ele usa para informar seus diagnósticos.

Os atributos de análise de código são declarados no namespace System.Diagnostics.CodeAnalysis.

Atributo Significado
AllowNull (§22.5.7.2) Um argumento não anulável pode ser null.
DisallowNull (ponto 22.5.7.3) Um argumento anulável nunca deve ser nulo.
MaybeNull (§22.5.7.6) Um valor de retorno não anulável pode ser null.
NotNull (ponto 22.5.7.8) Um valor de retorno anulável nunca será nulo.
MaybeNullWhen (ponto 22.5.7.7) Um argumento não anulável pode ser nulo quando o método retorna o valor especificado bool .
NotNullWhen (§22.5.7.10) Um argumento anulável não será nulo quando o método retornar o valor especificado bool .
NotNullIfNotNull (ponto 22.5.7.9) Um valor de retorno não será nulo se o argumento para o parâmetro especificado não for nulo.
DoesNotReturn (§22.5.7.4) Este método nunca retorna.
DoesNotReturnIf (§22.5.7.5) Esse método nunca retorna se o parâmetro associado bool tiver o valor especificado.

As secções seguintes do §22.5.7.1 são condicionalmente normativas.

22.5.7.2 O atributo AllowNull

Especifica que um valor nulo é permitido como uma entrada, mesmo que o tipo correspondente não o permita.

Exemplo: Considere a seguinte propriedade de leitura/gravação que nunca retorna null porque tem um valor padrão razoável. No entanto, um usuário pode dar null ao acessador de conjunto para definir a propriedade para 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 de conjunto dessa propriedade:

var v = new X();
v.ScreenName = null;   // may warn without attribute AllowNull

Sem o atributo, um compilador pode gerar um aviso porque a propriedade non-nullable-typed parece estar definida como um valor nulo. A presença do atributo suprime esse aviso. Exemplo final

22.5.7.3 O atributo DisallowNull

Especifica que um valor nulo não é permitido como uma entrada, mesmo que o tipo correspondente o permita.

Exemplo: Considere a seguinte propriedade 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, avisa os chamadores que, mesmo que possa ser nulo, os chamadores não devem defini-lo explicitamente como nulo. Exemplo final

22.5.7.4 O atributo DoesNotReturn

Especifica que um determinado método nunca retorna.

Exemplo: Considere o seguinte:

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 pode emitir um aviso se houver um caminho onde o método pode sair sem lançar 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. Em terceiro lugar, o código inacessível não afetará nenhum estado nulo.

O atributo não altera a acessibilidade (§13.2) ou a análise de atribuição definida (§9.4) com base na presença deste atributo. Ele é usado apenas para afetar avisos de anulabilidade. Exemplo final

22.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:

#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!;
}

Exemplo final

22.5.7.6 O atributo MaybeNull

Especifica que um valor de retorno 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 deste código é que, se T for substituído por string, T? torna-se uma anotação anulável. No entanto, este código não é legal porque T não está limitado a ser um tipo de referência. No entanto, adicionar esse 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 de retorno pode realmente ser null. Exemplo final

22.5.7.7 O atributo MaybeNullWhen

Especifica que um argumento não anulável pode ser null quando o método retorna o valor especificado bool . Isso é semelhante ao MaybeNull atributo (§22.5.7.6), mas inclui um parâmetro para o valor de retorno especificado.

22.5.7.8 O atributo NotNull

Especifica que um valor anulável nunca será null se o método retornar (em vez de lançar).

Exemplo: Considere o seguinte:

#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 os tipos de referência nulos são habilitados, o método ThrowWhenNull é compilado sem avisos. Quando esse método retorna, o value argumento é garantido como não null. No entanto, é aceitável chamar ThrowWhenNull com uma referência nula. Exemplo final

22.5.7.9 O atributo NotNullIfNotNull

Especifica que um valor de retorno não null é se o argumento para o parâmetro especificado não nullfor .

Exemplo: O estado nulo de um valor de retorno pode depender 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 certos argumentos não são null o atributo NotNullIfNotNull pode ser usado. Considere o seguinte método:

#nullable enable
string GetTopLevelDomainFromFullUrl(string url) { ... }

Se o url argumento não nullfor , null nã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 pode ser null, então o valor de retorno também pode ser null. Para expressar esse contrato corretamente, anote este método da seguinte forma:

#nullable enable
[return: NotNullIfNotNull("url")]
string? GetTopLevelDomainFromFullUrl(string? url) { ... }

Exemplo final

22.5.7.10 O atributo NotNullWhen

Especifica que um argumento anulável não será null quando o método retornar o valor especificado bool .

Exemplo: O método String.IsNullOrEmpty(String) library retorna true quando o argumento é null ou uma cadeia de caracteres vazia. É uma forma de null-check: os chamadores não precisam anular o argumento se o método retornar false. Para tornar um método como esse anulável, torne o tipo de parâmetro um tipo de referência anulável e adicione o atributo NotNullWhen:

#nullable enable
bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }

Exemplo final

22.6 Atributos para interoperação

Para interoperação com outros idiomas, um indexador pode ser implementado usando propriedades indexadas. Se nenhum IndexerName atributo estiver presente para um indexador, o nome Item será usado por padrão. O IndexerName atributo 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.

Exemplo final