Condividi tramite


22 Attributi

22.1 Generale

Gran parte del linguaggio C# consente al programmatore di specificare informazioni dichiarative sulle entità definite nel programma. Ad esempio, l'accessibilità di un metodo in una classe viene specificata decorata con il method_modifier, publicprotected, internale private.

C# consente ai programmatori di inventare nuovi tipi di informazioni dichiarative, denominati attributi. I programmatori possono quindi associare attributi a varie entità di programma e recuperare le informazioni sugli attributi in un ambiente di runtime.

Nota: ad esempio, un framework potrebbe definire un HelpAttribute attributo che può essere inserito in determinati elementi del programma (ad esempio classi e metodi) per fornire un mapping da tali elementi del programma alla relativa documentazione. nota finale

Gli attributi vengono definiti tramite la dichiarazione di classi di attributi (§22.2), che possono avere parametri posizionali e denominati (§22.2.3). Gli attributi sono associati alle entità in un programma C# usando le specifiche degli attributi (§22.3) e possono essere recuperati in fase di esecuzione come istanze dell'attributo (§22.4).

22.2 Classi di attributi

22.2.1 Generale

Una classe che deriva dalla classe System.Attributeastratta , direttamente o indirettamente, è una classe di attributi. La dichiarazione di una classe di attributi definisce un nuovo tipo di attributo che può essere inserito nelle entità del programma. Per convenzione, le classi di attributi vengono denominate con un suffisso .Attribute Gli usi di un attributo possono includere o omettere questo suffisso.

Una dichiarazione di classe generica non deve essere utilizzata System.Attribute come classe base diretta o indiretta.

Esempio:

public class B : Attribute {}
public class C<T> : B {} // Error – generic cannot be an attribute

esempio finale

22.2.2 Utilizzo degli attributi

L'attributo AttributeUsage (§22.5.2) viene usato per descrivere come usare una classe di attributi.

AttributeUsage ha un parametro posizionale (§22.2.3) che consente a una classe di attributi di specificare i tipi di entità programma in cui può essere usato.

Esempio: l'esempio seguente definisce una classe di attributi denominata SimpleAttribute che può essere inserita solo su class_declaratione interface_declaratione mostra diversi usi dell'attributo Simple .

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

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

Anche se questo attributo viene definito con il nome SimpleAttribute, quando viene usato questo attributo, il Attribute suffisso può essere omesso, con il nome Simplebreve . Di conseguenza, l'esempio precedente è semanticamente equivalente al seguente

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

esempio finale

AttributeUsage ha un parametro denominato (§22.2.3), denominato , che AllowMultipleindica se l'attributo può essere specificato più volte per una determinata entità. Se AllowMultiple per una classe di attributi è true, tale classe di attributi è una classe di attributi multi-uso e può essere specificata più volte in un'entità. Se AllowMultiple per una classe di attributi è false o non è specificato, tale classe di attributi è una classe di attributi a uso singolo e può essere specificata al massimo una volta in un'entità.

Esempio: l'esempio seguente definisce una classe di attributi multi-uso denominata AuthorAttribute e mostra una dichiarazione di classe con due usi dell'attributo Author :

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

esempio finale

AttributeUsage ha un altro parametro denominato (§22.2.3), denominato , che Inheritedindica se l'attributo, se specificato in una classe di base, viene ereditato anche dalle classi che derivano da tale classe di base. Se Inherited per una classe di attributi è true, tale attributo viene ereditato. Se Inherited per una classe di attributi è false, l'attributo non viene ereditato. Se non è specificato, il valore predefinito è true.

Una classe X di attributi non ha un AttributeUsage attributo associato, come in

class X : Attribute { ... }

equivale a quanto segue:

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

22.2.3 Parametri posizionali e denominati

Le classi di attributi possono avere parametriposizionali e parametridenominati s. Ogni costruttore di istanza pubblica per una classe di attributi definisce una sequenza valida di parametri posizionali per tale classe di attributi. Ogni campo di lettura/scrittura pubblico non statico e proprietà per una classe di attributi definisce un parametro denominato per la classe di attributi. Affinché una proprietà definisci un parametro denominato, tale proprietà deve disporre di una funzione di accesso get pubblica e di una funzione di accesso set pubblico.

Esempio: l'esempio seguente definisce una classe di attributi denominata HelpAttribute con un parametro posizionale, urle un parametro denominato, Topic. Sebbene non sia statico e pubblico, la proprietà Url non definisce un parametro denominato, poiché non è di lettura/scrittura. Vengono visualizzati anche due usi di questo attributo:

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

esempio finale

22.2.4 Tipi di parametri di attributo

I tipi di parametri posizionali e denominati per una classe di attributi sono limitati ai tipi di parametro dell'attributo, ovvero:

  • Uno dei tipi seguenti: , byte, , , float, int, ulonguintlongsbytestringshort. ushortdoublecharbool
  • Tipo object.
  • Tipo System.Type.
  • Tipi enumerazione.
  • Matrici unidimensionali dei tipi precedenti.
  • Un argomento del costruttore o un campo pubblico che non dispone di uno di questi tipi, non deve essere utilizzato come parametro posizionale o denominato in una specifica dell'attributo.

22.3 Specifica dell'attributo

La specifica dell'attributo è l'applicazione di un attributo definito in precedenza a un'entità programma. Un attributo è una parte di informazioni dichiarative aggiuntive specificate per un'entità programma. Gli attributi possono essere specificati nell'ambito globale (per specificare gli attributi nell'assembly o nel modulo contenitore) e per type_declaration (§14.7), class_member_declarations (§15.3), interface_member_declaration s (§14.7), class_member_declaration s (§15.3), interface_member_declarations (§15.3) 18.4), struct_member_declarations (§16.3), enum_member_declarations (§19.2), accessor_declarations (§15.7.3), event_accessor_ dichiaraziones (§15.8), elementi di parameter_lists (§15.6.2) ed elementi di type_parameter_lists (§15.2.3).

Gli attributi vengono specificati nelle sezioni dell'attributo. Una sezione dell'attributo è costituita da una coppia di parentesi quadre, che racchiudono un elenco delimitato da virgole di uno o più attributi. L'ordine in cui gli attributi vengono specificati in un elenco di questo tipo e l'ordine in cui le sezioni associate alla stessa entità del programma sono disposte, non sono significative. Ad esempio, le specifiche dell'attributo , , e [B, A] sono equivalenti[A][B]. [A, B][B][A]

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
    ;

Per il global_attribute_target di produzione e nel testo seguente, l'identificatore deve avere un ortografia uguale a assembly o module, dove l'uguaglianza è definita in §6.4.3. Per il attribute_target di produzione e nel testo seguente, l'identificatore deve avere un'ortografia diversa da assembly o module, usando la stessa definizione di uguaglianza di quanto sopra.

Un attributo è costituito da un attribute_name e da un elenco facoltativo di argomenti posizionali e denominati. Gli argomenti posizionali (se presenti) precedono gli argomenti denominati. Un argomento posizionale è costituito da un attribute_argument_expression. Un argomento denominato è costituito da un nome, seguito da un segno di uguale, seguito da un attribute_argument_expression, che insieme sono vincolati dalle stesse regole dell'assegnazione semplice. L'ordine degli argomenti denominati non è significativo.

Nota: per praticità, una virgola finale è consentita in un global_attribute_section e un attribute_section, proprio come è consentito in un array_initializer (§17.7). nota finale

Il attribute_name identifica una classe di attributi.

Quando un attributo viene inserito a livello globale, è necessario un global_attribute_target_specifier . Quando il global_attribute_target è uguale a:

  • assembly — la destinazione è l'assembly contenitore
  • module — la destinazione è il modulo contenitore

Non sono consentiti altri valori per global_attribute_target .

I nomi attribute_target standardizzati sono event, , methodfield, param, returnpropertytype, , e .typevar Questi nomi di destinazione devono essere usati solo nei contesti seguenti:

  • event — un evento.
  • field — un campo. Un evento simile a un campo (ad esempio uno senza funzioni di accesso) (§15.8.2) e una proprietà implementata automaticamente (§15.7.4) può anche avere un attributo con questa destinazione.
  • method : costruttore, finalizzatore, metodo, operatore, funzioni di accesso get e set di proprietà, funzioni di accesso get e set dell'indicizzatore e aggiunta e rimozione di eventi. Un evento simile a un campo (ad esempio uno senza funzioni di accesso) può avere anche un attributo con questa destinazione.
  • param — una funzione di accesso set di proprietà, una funzione di accesso set di indicizzatori, l'aggiunta e la rimozione di funzioni di accesso e un parametro in un costruttore, un metodo e un operatore.
  • property — una proprietà e un indicizzatore.
  • return : delegato, metodo, operatore, funzione di accesso get della proprietà e funzione di accesso get dell'indicizzatore.
  • type : delegato, classe, struct, enumerazione e interfaccia.
  • typevar — parametro di tipo.

Alcuni contesti consentono la specifica di un attributo su più di una destinazione. Un programma può specificare in modo esplicito la destinazione includendo un attribute_target_specifier. Senza un attribute_target_specifier viene applicato un valore predefinito, ma è possibile usare un attribute_target_specifier per affermare o ignorare l'impostazione predefinita. I contesti vengono risolti nel modo seguente:

  • Per un attributo in una dichiarazione delegato, la destinazione predefinita è il delegato. In caso contrario, quando il attribute_target è uguale a:
    • type — la destinazione è il delegato
    • return — la destinazione è il valore restituito
  • Per un attributo in una dichiarazione di metodo, la destinazione predefinita è il metodo . In caso contrario, quando il attribute_target è uguale a:
    • method — la destinazione è il metodo
    • return — la destinazione è il valore restituito
  • Per un attributo in una dichiarazione di operatore, la destinazione predefinita è l'operatore . In caso contrario, quando il attribute_target è uguale a:
    • method — la destinazione è l'operatore
    • return — la destinazione è il valore restituito
  • Per un attributo in una dichiarazione della funzione di accesso get per una proprietà o una dichiarazione dell'indicizzatore, la destinazione predefinita è il metodo associato. In caso contrario, quando il attribute_target è uguale a:
    • method — la destinazione è il metodo associato
    • return — la destinazione è il valore restituito
  • Per un attributo specificato in una funzione di accesso set per una proprietà o una dichiarazione dell'indicizzatore, la destinazione predefinita è il metodo associato. In caso contrario, quando il attribute_target è uguale a:
    • method — la destinazione è il metodo associato
    • param — la destinazione è il parametro implicito solitario
  • Per un attributo in una dichiarazione di proprietà implementata automaticamente, la destinazione predefinita è la proprietà . In caso contrario, quando il attribute_target è uguale a:
    • field — la destinazione è il campo sottostante generato dal compilatore per la proprietà
  • Per un attributo specificato in una dichiarazione di evento che omette event_accessor_declarations la destinazione predefinita è la dichiarazione di evento. In caso contrario, quando il attribute_target è uguale a:
    • event — la destinazione è la dichiarazione di evento
    • field — la destinazione è il campo
    • method — le destinazioni sono i metodi
  • Nel caso di una dichiarazione di evento che non omette event_accessor_declarations la destinazione predefinita è il metodo .
    • method — la destinazione è il metodo associato
    • param — la destinazione è il parametro solitario

In tutti gli altri contesti, l'inclusione di un attribute_target_specifier è consentita ma non necessaria.

Esempio: una dichiarazione di classe può includere o omettere l'identificatore type:

[type: Author("Brian Kernighan")]
class Class1 {}

[Author("Dennis Ritchie")]
class Class2 {}

esempio finale.

Un'implementazione può accettare altri attribute_target, a scopo di implementazione definita. Un'implementazione che non riconosce tale attribute_target genera un avviso e ignora l'attribute_section contenitore.

Per convenzione, le classi di attributi vengono denominate con un suffisso .Attribute Un attribute_name può includere o omettere questo suffisso. In particolare, un attribute_name viene risolto nel modo seguente:

  • Se l'identificatore più a destra del attribute_name è un identificatore verbatim (§6.4.3), il attribute_name viene risolto come type_name (§7.8). Se il risultato non è un tipo derivato da System.Attribute, si verifica un errore in fase di compilazione.
  • In caso contrario, .
    • Il attribute_name viene risolto come type_name (§7.8), ad eccezione di eventuali errori eliminati. Se la risoluzione ha esito positivo e restituisce un tipo derivato da System.Attribute allora il tipo è il risultato di questo passaggio.
    • I caratteri Attribute vengono aggiunti all'identificatore più a destra nella attribute_name e la stringa risultante di token viene risolta come type_name (§7.8), ad eccezione di eventuali errori eliminati. Se la risoluzione ha esito positivo e restituisce un tipo derivato da System.Attribute allora il tipo è il risultato di questo passaggio.

Se esattamente uno dei due passaggi precedenti restituisce un tipo derivato da System.Attribute, tale tipo è il risultato del attribute_name. In caso contrario, si verifica un errore in fase di compilazione.

Esempio: se viene trovata una classe di attributi con e senza questo suffisso, è presente un'ambiguità e viene restituito un errore in fase di compilazione. Se il attribute_name viene digitato in modo che l'identificatore più a destra sia un identificatore verbatim (§6.4.3), viene trovata una corrispondenza solo con un attributo senza suffisso, consentendo così la risoluzione di tale ambiguità. L'esempio:

[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 due classi di attributi denominate Example e ExampleAttribute. L'attributo [Example] è ambiguo, poiché può fare riferimento a Example o ExampleAttribute. L'uso di un identificatore verbatim consente di specificare la finalità esatta in casi rari. L'attributo [ExampleAttribute] non è ambiguo (anche se si tratta di una classe di attributi denominata ExampleAttributeAttribute!). Se la dichiarazione per la classe Example viene rimossa, entrambi gli attributi fanno riferimento alla classe di attributi denominata ExampleAttribute, come indicato di seguito:

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

esempio finale

Si tratta di un errore in fase di compilazione per usare più volte una classe di attributi a uso singolo nella stessa entità.

Esempio: esempio

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

restituisce un errore in fase di compilazione perché tenta di usare HelpString, che è una classe di attributi a uso singolo, più volte nella dichiarazione di Class1.

esempio finale

Un'espressione E è un attribute_argument_expression se tutte le istruzioni seguenti sono vere:

  • Il tipo di è un tipo di parametro di E attributo (§22.2.4).
  • In fase di compilazione, il valore di E può essere risolto in uno dei modi seguenti:
    • Valore costante di .
    • Oggetto System.Type ottenuto utilizzando un typeof_expression (§12.8.18) che specifica un tipo non generico, un tipo costruito chiuso (§8.4.3) o un tipo generico non associato (§8.4.4), ma non un tipo aperto (§8.4.3).
    • Matrice unidimensionale di attribute_argument_expressions.

Esempio:

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

esempio finale

Gli attributi di un tipo dichiarato in più parti sono determinati combinando, in un ordine non specificato, gli attributi di ognuna delle relative parti. Se lo stesso attributo viene posizionato su più parti, equivale a specificare l'attributo più volte nel tipo.

Esempio: le due parti:

[Attr1, Attr2("hello")]
partial class A {}

[Attr3, Attr2("goodbye")]
partial class A {}

sono equivalenti alla dichiarazione singola seguente:

[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")]
class A {}

esempio finale

Gli attributi sui parametri di tipo si combinano nello stesso modo.

22.4 Istanze degli attributi

22.4.1 Generale

Un'istanza dell'attributo è un'istanza che rappresenta un attributo in fase di esecuzione. Un attributo viene definito con una classe di attributi, argomenti posizionali e argomenti denominati. Un'istanza dell'attributo è un'istanza della classe di attributi inizializzata con gli argomenti posizionali e denominati.

Il recupero di un'istanza dell'attributo comporta sia l'elaborazione in fase di compilazione che di runtime, come descritto nelle sottoclause seguenti.

22.4.2 Compilazione di un attributo

La compilazione di un attributo con la classe Tdi attributi , positional_argument_list P , named_argument_listN e specificata in un'entità E programma viene compilata in un assembly A tramite la procedura seguente:

  • Seguire i passaggi di elaborazione in fase di compilazione per compilare un object_creation_expression del modulo nuovo T(P). Questi passaggi generano un errore in fase di compilazione o determinano un costruttore C di istanza in T che può essere richiamato in fase di esecuzione.
  • Se C non dispone di accessibilità pubblica, si verifica un errore in fase di compilazione.
  • Per ogni named_argument Arg in N:
    • Si supponga di Name essere l'identificatore del named_argument Arg.
    • Name identifica un campo o una proprietà pubblica di lettura non statica in T. Se T non ha un campo o una proprietà di questo tipo, si verifica un errore in fase di compilazione.
  • Se uno dei valori all'interno di positional_argument_listP o uno dei valori all'interno di named_argument_listN è di tipo System.String e il valore non è ben formato come definito dallo standard Unicode, è definito dall'implementazione se il valore compilato è uguale al valore di runtime recuperato (§22.4.3).

    Nota: ad esempio, una stringa che contiene un'unità di codice UTF-16 surrogata elevata che non è immediatamente seguita da un'unità di codice surrogata bassa non è ben formata. nota finale

  • Archiviare le informazioni seguenti (per la creazione di istanze in fase di esecuzione dell'attributo) nell'output dell'assembly dal compilatore in seguito alla compilazione del programma contenente l'attributo: la classe Tdell'attributo , il costruttore C dell'istanza in T, l'positional_argument_list P , il named_argument_listN e l'entità Eprogramma associata , con i valori risolti completamente in fase di compilazione.

22.4.3 Recupero in fase di esecuzione di un'istanza dell'attributo

Usando i termini definiti in §22.4.2, l'istanza dell'attributo rappresentata da T, PC, e Ne associata a E può essere recuperata in fase di esecuzione dall'assembly A seguendo questa procedura:

  • Seguire i passaggi di elaborazione in fase di esecuzione per l'esecuzione di un object_creation_expression del modulo new T(P), usando il costruttore C e i valori dell'istanza come determinato in fase di compilazione. Questi passaggi generano un'eccezione o producono un'istanza O di T.
  • Per ogni named_argument Arg in N, in ordine:
    • Si supponga di Name essere l'identificatore del named_argument Arg. Se Name non identifica un campo di lettura/scrittura pubblico non statico o una proprietà in O, viene generata un'eccezione.
    • Si supponga di Value valutare il attribute_argument_expression di Arg.
    • Se Name identifica un campo in O, impostare questo campo su Value.
    • In caso contrario, Name identifica una proprietà in O. Impostare questa proprietà su Value.
    • Il risultato è O, un'istanza della classe T di attributi inizializzata con il positional_argument_list P e l'named_argument_listN.

Nota: il formato per l'archiviazione Tdi , C, P, N (e associarlo a E) in A e il meccanismo per specificare E e recuperare T, , C, PN da A (e di conseguenza il modo in cui un'istanza di attributo viene ottenuta in fase di esecuzione) non rientra nell'ambito di questa specifica. nota finale

Esempio: in un'implementazione dell'interfaccia della riga di comando, è possibile recuperare le Help istanze dell'attributo nell'assembly creato compilando il programma di esempio in §22.2.3 con il programma seguente:

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

esempio finale

22.5 Attributi riservati

22.5.1 Generale

Un certo numero di attributi influisce sulla lingua in qualche modo. Gli attributi disponibili sono:

  • System.AttributeUsageAttribute (§22.5.2), usato per descrivere i modi in cui è possibile usare una classe di attributi.
  • System.Diagnostics.ConditionalAttribute (§22.5.3) è una classe di attributi multi-uso usata per definire metodi condizionali e classi di attributi condizionali. Questo attributo indica una condizione testando un simbolo di compilazione condizionale.
  • System.ObsoleteAttribute (§22.5.4), utilizzato per contrassegnare un membro come obsoleto.
  • System.Runtime.CompilerServices.AsyncMethodBuilderAttribute (§22.5.5), usato per stabilire un generatore di attività per un metodo asincrono.
  • 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), utilizzati per fornire informazioni sul contesto chiamante ai parametri facoltativi.

Gli attributi di analisi statica nullable (§22.5.7) possono migliorare la correttezza degli avvisi generati per le capacità Null e gli stati Null (§8.9.5).

Un ambiente di esecuzione può fornire attributi aggiuntivi definiti dall'implementazione che influiscono sull'esecuzione di un programma C#.

22.5.2 AttributoUsage

L'attributo AttributeUsage viene usato per descrivere il modo in cui è possibile usare la classe di attributi.

Una classe decorata con l'attributo AttributeUsage deve derivare da System.Attribute, direttamente o indirettamente. In caso contrario, si verifica un errore in fase di compilazione.

Nota: per un esempio di utilizzo di questo attributo, vedere §22.2.2. nota finale

22.5.3 Attributo condizionale

22.5.3.1 Generale

L'attributo Conditional abilita la definizione di metodi condizionali e classi di attributi condizionali.

22.5.3.2 Metodi condizionali

Un metodo decorato con l'attributo Conditional è un metodo condizionale. Ogni metodo condizionale è quindi associato ai simboli di compilazione condizionale dichiarati nei relativi Conditional attributi.

Esempio:

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

Eg.M dichiara come metodo condizionale associato ai due simboli ALPHA di compilazione condizionale e BETA.

esempio finale

Viene inclusa una chiamata a un metodo condizionale se uno o più simboli di compilazione condizionale associati vengono definiti al momento della chiamata, altrimenti la chiamata viene omessa.

Un metodo condizionale è soggetto alle restrizioni seguenti:

  • Il metodo condizionale deve essere un metodo in un class_declaration o struct_declaration. Si verifica un errore in fase di compilazione se l'attributo Conditional viene specificato in un metodo in una dichiarazione di interfaccia.
  • Il metodo condizionale deve avere un tipo restituito di void.
  • Il metodo condizionale non deve essere contrassegnato con il override modificatore. Un metodo condizionale può tuttavia essere contrassegnato con il virtual modificatore. Le sostituzioni di tale metodo sono condizionali in modo implicito e non devono essere contrassegnate in modo esplicito con un Conditional attributo .
  • Il metodo condizionale non deve essere un'implementazione di un metodo di interfaccia. In caso contrario, si verifica un errore in fase di compilazione.
  • I parametri del metodo condizionale non devono essere parametri di output.

Inoltre, si verifica un errore in fase di compilazione se un delegato viene creato da un metodo condizionale.

Esempio: esempio

#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();
    }
}

Class1.M dichiara come metodo condizionale. Class2Il metodo chiama Test questo metodo. Poiché il simbolo DEBUG di compilazione condizionale è definito, se Class2.Test viene chiamato , chiamerà M. Se il simbolo DEBUG non fosse stato definito, Class2.Test non chiamerebbe Class1.M.

esempio finale

È importante comprendere che l'inclusione o l'esclusione di una chiamata a un metodo condizionale è controllata dai simboli di compilazione condizionale al momento della chiamata.

Esempio: nel codice seguente

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

le Class2 classi e Class3 ognuna contiene chiamate al metodo Class1.Fcondizionale , che è condizionale in base al fatto che sia definito o meno DEBUG . Poiché questo simbolo è definito nel contesto di Class2 ma non Class3, la chiamata a F in Class2 è inclusa, mentre la chiamata a in F Class3 viene omessa.

esempio finale

L'uso di metodi condizionali in una catena di ereditarietà può generare confusione. Le chiamate effettuate a un metodo condizionale tramite base, del formato base.M, sono soggette alle normali regole di chiamata al metodo condizionale.

Esempio: nel codice seguente

// 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 include una chiamata all'oggetto M definito nella relativa classe di base. Questa chiamata viene omessa perché il metodo di base è condizionale in base alla presenza del simbolo DEBUG, che non è definito. Pertanto, il metodo scrive solo nella console "Class2.M executed". L'uso di pp_declarationdi pp_declaration può eliminare tali problemi.

esempio finale

22.5.3.3 Classi di attributi condizionali

Una classe di attributi (§22.2) decorata con uno o più Conditional attributi è una classe di attributi condizionale. Una classe di attributi condizionali è quindi associata ai simboli di compilazione condizionale dichiarati nei relativi Conditional attributi.

Esempio:

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

TestAttribute dichiara come classe di attributo condizionale associata ai simboli ALPHA di compilazione condizionale e BETA.

esempio finale

Le specifiche degli attributi (§22.3) di un attributo condizionale vengono incluse se uno o più simboli di compilazione condizionale associati sono definiti al momento della specifica, altrimenti la specifica dell'attributo viene omessa.

È importante notare che l'inclusione o l'esclusione di una specifica di attributo di una classe di attributi condizionali è controllata dai simboli di compilazione condizionale al punto della specifica.

Esempio: nell'esempio

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

le classi Class1 e Class2 sono ognuna decorata con l'attributo Test, che è condizionale in base al fatto che sia definito o meno DEBUG . Poiché questo simbolo è definito nel contesto di Class1 ma non Class2, viene inclusa la specifica dell'attributo Test in Class1 , mentre la specifica dell'attributo Test su Class2 viene omessa.

esempio finale

22.5.4 Attributo obsoleto

L'attributo Obsolete viene usato per contrassegnare tipi e membri di tipi che non devono più essere usati.

Se un programma utilizza un tipo o un membro decorato con l'attributo Obsolete , il compilatore genererà un avviso o un errore. In particolare, il compilatore genera un avviso se non viene specificato alcun parametro di errore o se viene specificato il parametro di errore e ha il valore false. Il compilatore genera un errore se il parametro di errore viene specificato e ha il valore true.

Esempio: nel codice seguente

[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();
    }
}

la classe A è decorata con l'attributo Obsolete . Ogni uso di A in Main genera un avviso che include il messaggio specificato, "Questa classe è obsoleta; usare invece la classe B ".

esempio finale

22.5.5 L'attributo AsyncMethodBuilder

Questo attributo è descritto in §15.15.1.

22.5.6 Attributi caller-info

22.5.6.1 Generale

Ai fini della registrazione e della creazione di report, a volte è utile per un membro della funzione ottenere determinate informazioni in fase di compilazione sul codice chiamante. Gli attributi caller-info forniscono un modo per passare tali informazioni in modo trasparente.

Quando un parametro facoltativo viene annotato con uno degli attributi caller-info, l'omissione dell'argomento corrispondente in una chiamata non comporta necessariamente la sostituzione del valore del parametro predefinito. Se invece sono disponibili le informazioni specificate sul contesto chiamante, tali informazioni verranno passate come valore dell'argomento.

Esempio:

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

Una chiamata a Log() senza argomenti stampa il numero di riga e il percorso del file della chiamata, nonché il nome del membro all'interno del quale si è verificata la chiamata.

esempio finale

Gli attributi delle informazioni sul chiamante possono verificarsi in qualsiasi punto dei parametri facoltativi, incluse le dichiarazioni di delegato. Tuttavia, gli attributi specifici delle informazioni sul chiamante hanno restrizioni sui tipi dei parametri che possono attribuire, in modo che sia sempre presente una conversione implicita da un valore sostituito al tipo di parametro.

Si tratta di un errore per avere lo stesso attributo caller-info su un parametro sia della definizione che dell'implementazione di una dichiarazione di metodo parziale. Vengono applicati solo gli attributi caller-info nella parte di definizione, mentre gli attributi delle informazioni chiamanti che si verificano solo nella parte di implementazione vengono ignorati.

Le informazioni sul chiamante non influiscono sulla risoluzione dell'overload. Poiché i parametri facoltativi con attributi vengono comunque omessi dal codice sorgente del chiamante, la risoluzione dell'overload ignora tali parametri nello stesso modo in cui ignora altri parametri facoltativi omessi (§12.6.4).

Le informazioni sul chiamante vengono sostituite solo quando una funzione viene richiamata in modo esplicito nel codice sorgente. Le chiamate implicite, ad esempio le chiamate implicite al costruttore padre, non hanno un percorso di origine e non sostituiranno le informazioni del chiamante. Inoltre, le chiamate associate dinamicamente non sostituiranno le informazioni del chiamante. Quando un parametro con attributi caller-info viene omesso in questi casi, viene invece usato il valore predefinito specificato del parametro.

Un'eccezione è l'espressione di query. Queste sono considerate espansioni sintattiche e, se le chiamate espandono per omettere parametri facoltativi con attributi caller-info, le informazioni del chiamante verranno sostituite. Il percorso usato è il percorso della clausola di query da cui è stata generata la chiamata.

Se in un determinato parametro viene specificato più di un attributo caller-info, vengono riconosciuti nell'ordine seguente: CallerLineNumber, CallerFilePath, CallerMemberName. Si consideri la dichiarazione di parametro seguente:

[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...

CallerLineNumber ha la precedenza e gli altri due attributi vengono ignorati. Se CallerLineNumber venisse omesso, CallerFilePath avrebbe la precedenza e CallerMemberName verrebbe ignorato. L'ordinamento lessicale di questi attributi è irrilevante.

22.5.6.2 Attributo CallerLineNumber

L'attributo System.Runtime.CompilerServices.CallerLineNumberAttribute è consentito sui parametri facoltativi quando è presente una conversione implicita standard (§10.4.2) dal valore int.MaxValue costante al tipo del parametro. In questo modo si garantisce che qualsiasi numero di riga non negativo fino a tale valore possa essere passato senza errori.

Se una chiamata di funzione da una posizione nel codice sorgente omette un parametro facoltativo con CallerLineNumberAttribute, un valore letterale numerico che rappresenta il numero di riga della posizione viene usato come argomento per la chiamata anziché il valore del parametro predefinito.

Se la chiamata si estende su più righe, la riga scelta dipende dall'implementazione.

Il numero di riga può essere interessato dalle #line direttive (§6.5.8).

22.5.6.3 Attributo CallerFilePath

L'attributo System.Runtime.CompilerServices.CallerFilePathAttribute è consentito sui parametri facoltativi quando è presente una conversione implicita standard (§10.4.2) da string al tipo del parametro.

Se una chiamata di funzione da un percorso nel codice sorgente omette un parametro facoltativo con CallerFilePathAttribute, un valore letterale stringa che rappresenta il percorso del file del percorso viene usato come argomento per la chiamata anziché il valore del parametro predefinito.

Il formato del percorso del file dipende dall'implementazione.

Il percorso del file può essere interessato dalle #line direttive (§6.5.8).

22.5.6.4 Attributo CallerMemberName

L'attributo System.Runtime.CompilerServices.CallerMemberNameAttribute è consentito sui parametri facoltativi quando è presente una conversione implicita standard (§10.4.2) da string al tipo del parametro.

Se una chiamata di funzione da una posizione all'interno del corpo di un membro della funzione o all'interno di un attributo applicato al membro della funzione stessa o al relativo tipo restituito, parametri o parametri di tipo nel codice sorgente omette un parametro facoltativo con CallerMemberNameAttribute, un valore letterale stringa che rappresenta il nome del membro viene utilizzato come argomento per la chiamata anziché il valore del parametro predefinito.

Per le chiamate che si verificano all'interno di metodi generici, viene usato solo il nome del metodo stesso, senza l'elenco di parametri di tipo.

Per le chiamate che si verificano all'interno di implementazioni esplicite del membro dell'interfaccia, viene usato solo il nome del metodo stesso, senza la qualifica dell'interfaccia precedente.

Per le chiamate che si verificano all'interno di funzioni di accesso a proprietà o eventi, il nome del membro utilizzato è quello della proprietà o dell'evento stesso.

Per le chiamate che si verificano all'interno delle funzioni di accesso dell'indicizzatore, il nome del membro utilizzato è quello fornito da un IndexerNameAttribute oggetto (§22.6) sul membro dell'indicizzatore, se presente o il nome Item predefinito in caso contrario.

Per le chiamate che si verificano all'interno di inizializzatori di campi o eventi, il nome del membro utilizzato è il nome del campo o dell'evento da inizializzare.

Per le chiamate che si verificano all'interno di dichiarazioni di costruttori di istanza, costruttori statici, finalizzatori e operatori il nome del membro usato è dipendente dall'implementazione.

22.5.7 Attributi di analisi codice

22.5.7.1 Generale

Gli attributi di questa sezione vengono usati per fornire informazioni aggiuntive per supportare un compilatore che fornisce valori Null e diagnostica dello stato Null (§8.9.5). Non è necessario un compilatore per eseguire alcuna diagnostica dello stato Null. La presenza o l'assenza di questi attributi non influiscono sulla lingua né sul comportamento di un programma. Un compilatore che non fornisce la diagnostica dello stato Null deve leggere e ignorare la presenza di questi attributi. Un compilatore che fornisce la diagnostica dello stato Null userà il significato definito in questa sezione per uno di questi attributi che usa per informare la diagnostica.

Gli attributi di analisi del codice vengono dichiarati nello spazio dei nomi System.Diagnostics.CodeAnalysis.

Attributo significato
AllowNull (§22.5.7.2) Un argomento non nullable può essere Null.
DisallowNull (§22.5.7.3) Un argomento nullable non deve mai essere Null.
MaybeNull (§22.5.7.6) Un valore restituito non nullable può essere Null.
NotNull (§22.5.7.8) Un valore restituito nullable non sarà mai Null.
MaybeNullWhen (§22.5.7.7) Un argomento non nullable può essere Null quando il metodo restituisce il valore specificato bool.
NotNullWhen (§22.5.7.10) Un argomento nullable non sarà Null quando il metodo restituisce il valore specificato bool .
NotNullIfNotNull (§22.5.7.9) Un valore restituito non è Null se l'argomento per il parametro specificato non è Null.
DoesNotReturn (§22.5.7.4) Questo metodo non restituisce mai.
DoesNotReturnIf (§22.5.7.5) Questo metodo non restituisce mai se il parametro associato bool ha il valore specificato.

Le sezioni seguenti di §22.5.7.1 sono normative condizionali.

22.5.7.2 Attributo AllowNull

Specifica che un valore Null è consentito come input anche se il tipo corrispondente non lo consente.

Esempio: si consideri la seguente proprietà di lettura/scrittura che non restituisce null mai perché ha un valore predefinito ragionevole. Tuttavia, un utente può assegnare null alla funzione di accesso set per impostare la proprietà su tale valore predefinito.

#nullable enable
public class X
{
    [AllowNull]
    public string ScreenName
    {
        get => _screenName;
        set => _screenName = value ?? GenerateRandomScreenName();
    }
    private string _screenName = GenerateRandomScreenName();
    private static string GenerateRandomScreenName() => ...;
}

Data l'uso seguente della funzione di accesso set di tale proprietà

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

senza l'attributo , il compilatore può generare un avviso perché la proprietà tipizzata non nullable sembra essere impostata su un valore Null. La presenza dell'attributo elimina l'avviso. esempio finale

22.5.7.3 Attributo DisallowNull

Specifica che un valore Null non è consentito come input anche se il tipo corrispondente lo consente.

Esempio: si consideri la proprietà seguente in cui null è il valore predefinito, ma i client possono impostarlo solo su un valore non Null.

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

La funzione di accesso get potrebbe restituire il valore predefinito di null, in modo che il compilatore possa avvisare che deve essere controllato prima dell'accesso. Inoltre, avvisa i chiamanti che, anche se potrebbe essere Null, i chiamanti non devono impostarlo in modo esplicito su Null. esempio finale

22.5.7.4 Attributo DoesNotReturn

Specifica che un metodo specificato non restituisce mai.

Esempio: considerare quanto segue:

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

La presenza dell'attributo consente al compilatore in diversi modi. In primo luogo, il compilatore può generare un avviso se è presente un percorso in cui il metodo può uscire senza generare un'eccezione. In secondo luogo, il compilatore può eliminare gli avvisi nullable in qualsiasi codice dopo una chiamata a tale metodo, fino a quando non viene trovata una clausola catch appropriata. In terzo luogo, il codice non raggiungibile non influirà sugli stati Null.

L'attributo non modifica la raggiungibilità (§13.2) o l'assegnazione definita (§9.4) in base alla presenza di questo attributo. Viene usato solo per influire sugli avvisi di nullità. esempio finale

22.5.7.5 L'attributo DoesNotReturnIf

Specifica che un metodo specificato non restituisce mai se il parametro associato bool ha il valore specificato.

Esempio: considerare quanto segue:

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

esempio finale

22.5.7.6 Attributo MaybeNull

Specifica che un valore restituito non nullable può essere Null.

Esempio: considerare il metodo generico seguente:

#nullable enable
public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }

L'idea di questo codice è che se T viene sostituito da string, T? diventa un'annotazione nullable. Tuttavia, questo codice non è valido perché T non è vincolato a essere un tipo di riferimento. Tuttavia, l'aggiunta di questo attributo risolve il problema:

#nullable enable
[return: MaybeNull]
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }

L'attributo informa i chiamanti che il contratto implica un tipo non nullable, ma il valore restituito può effettivamente essere null. esempio finale

22.5.7.7 L'attributo MaybeNullWhen

Specifica che un argomento non nullable può essere null quando il metodo restituisce il valore specificato bool . È simile all'attributo MaybeNull (§22.5.7.6), ma include un parametro per il valore restituito specificato.

22.5.7.8 Attributo NotNull

Specifica che un valore nullable non sarà null mai se il metodo restituisce (anziché generare un'eccezione).

Esempio: considerare quanto segue:

#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 i tipi riferimento Null sono abilitati, il metodo ThrowWhenNull viene compilato senza avvisi. Quando termina, l'argomento value è garantito che non nullsia . Tuttavia, è accettabile chiamare ThrowWhenNull con un riferimento Null. esempio finale

22.5.7.9 Attributo NotNullIfNotNull

Specifica che un valore restituito non null è se l'argomento per il parametro specificato non nullè .

Esempio: lo stato Null di un valore restituito può dipendere dallo stato Null di uno o più argomenti. Per facilitare l'analisi del compilatore quando un metodo restituisce sempre un valore non Null quando alcuni argomenti non null sono l'attributo NotNullIfNotNull può essere usato. Si consideri il modello seguente:

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

Se l'argomento url non nullè , null non viene restituito. Quando i riferimenti nullable sono abilitati, tale firma funziona correttamente, purché l'API non accetti mai un argomento Null. Tuttavia, se l'argomento potrebbe essere Null, anche il valore restituito potrebbe essere Null. Per esprimere correttamente il contratto, annotare questo metodo come segue:

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

esempio finale

22.5.7.10 Attributo NotNullWhen

Specifica che un argomento nullable non sarà null quando il metodo restituisce il valore specificato bool .

Esempio: il metodo String.IsNullOrEmpty(String) di libreria restituisce true quando l'argomento è null o una stringa vuota. Si tratta di una forma di controllo null: i chiamanti non devono controllare null-check l'argomento se il metodo restituisce false. Per rendere compatibile un metodo come questo nullable, rendere il tipo di parametro un tipo riferimento nullable e aggiungere l'attributo NotNullWhen:

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

esempio finale

22.6 Attributi per l'interoperabilità

Per l'interoperabilità con altri linguaggi, un indicizzatore può essere implementato usando le proprietà indicizzate. Se non è presente alcun IndexerName attributo per un indicizzatore, il nome Item viene usato per impostazione predefinita. L'attributo IndexerName consente a uno sviluppatore di eseguire l'override di questo valore predefinito e di specificare un nome diverso.

Esempio: per impostazione predefinita, il nome di un indicizzatore è Item. È possibile eseguire l'override, come indicato di seguito:

[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
    get { ... }
    set { ... }
}

Il nome dell'indicizzatore è TheItemora .

esempio finale