Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
23.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, denominate attributis. 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
HelpAttributeattributo 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 (§23.2), che possono avere parametri posizionali e denominati (§23.2.3). Gli attributi sono associati alle entità in un programma C# usando le specifiche degli attributi (§23.3) e possono essere recuperati in fase di esecuzione come istanze di attributo (§23.4).
23.2 Classi di attributi
23.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 attributeesempio finale
23.2.2 Utilizzo degli attributi
L'attributo AttributeUsage (§23.5.2) viene usato per descrivere come usare una classe di attributi.
AttributeUsage ha un parametro posizionale (§23.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
SimpleAttributeche può essere inserita solo su class_declaratione interface_declaratione mostra diversi usi dell'attributoSimple.[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, ilAttributesuffisso può essere omesso, con il nomeSimplebreve . Di conseguenza, l'esempio precedente è semanticamente equivalente al seguente[SimpleAttribute] class Class1 {...} [SimpleAttribute] interface Interface1 {...}esempio finale
AttributeUsage ha un parametro denominato (§23.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
AuthorAttributee mostra una dichiarazione di classe con due usi dell'attributoAuthor:[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 (§23.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 { ... }
23.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
HelpAttributecon un parametro posizionale,urle un parametro denominato,Topic. Sebbene non sia statico e pubblico, la proprietàUrlnon 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
23.2.4 Tipi di parametri di attributo
I tipi di parametri posizionali e denominati per una classe di attributi sono limitati al tipo di parametro dell'attributo, ovvero:
- Uno dei tipi seguenti: ,
bool, , ,byte,char,doublefloatintlongsbyteshort.stringuintulongushort - 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.
23.3 Specifica dell'attributo
L'applicazione di un attributo definito in precedenza a un'entità di programma è detta specifica dell'attributo. Un attributo è una parte di informazioni dichiarative aggiuntive specificate per un'entità programma. Gli attributi possono essere specificati nell'ambito globale (per specificare attributi nell'assembly o nel modulo contenitore) e per type_declarations (§14.7), class_member_declarations (§15.3), interface_member_declarations (§19.7) 4), struct_member_declarations (§16.3), enum_member_declarations (§20.2), accessor_declarations (§15.7.3), event_accessor_declarations (§15.8)), elementi di parameter_list(§15.6.2) e elementi di type_parameter_lists (§15.2.3).
Gli attributi vengono specificati nella sezione dell'attributos. 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 [A][B] sono equivalenti[B][A]. [A, B][B, A]
global_attributes
: global_attribute_section+
;
global_attribute_section
: '[' global_attribute_target_specifier attribute_list ']'
;
global_attribute_target_specifier
: global_attribute_target ':'
;
global_attribute_target
: identifier
;
attributes
: attribute_section+
;
attribute_section
: '[' attribute_target_specifier? attribute_list ']'
;
attribute_target_specifier
: attribute_target ':'
;
attribute_target
: identifier
| keyword
;
attribute_list
: attribute (',' attribute)* ','?
;
attribute
: attribute_name attribute_arguments?
;
attribute_name
: type_name
;
attribute_arguments
: '(' ')'
| '(' positional_argument_list (',' named_argument_list)? ')'
| '(' named_argument_list ')'
;
positional_argument_list
: positional_argument (',' positional_argument)*
;
positional_argument
: argument_name? attribute_argument_expression
;
named_argument_list
: named_argument (',' named_argument)*
;
named_argument
: identifier '=' attribute_argument_expression
;
attribute_argument_expression
: non_assignment_expression
;
Per il global_attribute_target di produzione e nel testo seguente, l'identificatore deve avere un ortografia uguale a o assembly, dove l'uguaglianza è definita in module. Per il attribute_target di produzione
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 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.Attributeallora il tipo è il risultato di questo passaggio. - I caratteri
Attributevengono 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 daSystem.Attributeallora il tipo è il risultato di questo passaggio.
- 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
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
ExampleeExampleAttribute. L'attributo[Example]è ambiguo, poiché può fare riferimento aExampleoExampleAttribute. L'uso di un identificatore verbatim consente di specificare la finalità esatta in casi rari. L'attributo[ExampleAttribute]non è ambiguo (anche se esistesse una classe di attributi denominataExampleAttributeAttribute!). Se la dichiarazione per la classeExampleviene rimossa, entrambi gli attributi fanno riferimento alla classe di attributi denominataExampleAttribute, 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 diClass1.esempio finale
Un'espressione E è un attribute_argument_expression se tutte le istruzioni seguenti sono vere:
- Il tipo di è un tipo di parametro di
Eattributo (§23.2.4). - In fase di compilazione, il valore di
Epuò essere risolto in uno dei modi seguenti:
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.
23.4 Istanze di attributi
23.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.
23.4.2 Compilazione di un attributo
La compilazione di un , T , named_argument_listP e specificata in un'entità programma viene compilata in un assembly N 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 costruttoreCdi istanza inTche può essere richiamato in fase di esecuzione. - Se
Cnon dispone di accessibilità pubblica, si verifica un errore in fase di compilazione. - Per ogni named_argument
ArginN:- Si supponga di
Nameessere l'identificatore del named_argumentArg. -
Nameidentifica un campo o una proprietà pubblica di lettura non statica inT. SeTnon ha un campo o una proprietà di questo tipo, si verifica un errore in fase di compilazione.
- Si supponga di
- Se uno dei valori all'interno di positional_argument_list
Po uno dei valori all'interno di named_argument_listNè di tipoSystem.Stringe il valore non è ben formato come definito dallo standard Unicode, è definito dall'implementazione se il valore compilato è uguale al valore di runtime recuperato (§23.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 costruttoreCdell'istanza inT, l'positional_argument_list , ilPe l'entitàNprogramma associata , con i valori risolti completamente in fase di compilazione.
23.4.3 Recupero in fase di esecuzione di un'istanza dell'attributo
Usando i termini definiti in §23.4.2, l'istanza dell'attributo rappresentata da T, CP, e Nassociata 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 di compilazione. Questi passaggi generano un'eccezione o producono un'istanza
OdiT. - Per ogni named_argument
ArginN, in ordine:- Si supponga di
Nameessere l'identificatore del named_argumentArg. SeNamenon identifica un campo di lettura/scrittura pubblico non statico o una proprietà inO, viene generata un'eccezione. - Si supponga di
Valuevalutare il attribute_argument_expression diArg. - Se
Nameidentifica un campo inO, impostare questo campo suValue. - In caso contrario, Name identifica una proprietà in
O. Impostare questa proprietà su Value. - Il risultato è
O, un'istanza della classeTdi attributi inizializzata con il e l'named_argument_listP.
- Si supponga di
Nota: il formato per l'archiviazione
Tdi ,C,P,N(e associarlo aE) inAe il meccanismo per specificareEe recuperareT, ,C,PNdaA(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
23.5 Attributi riservati
23.5.1 Generale
Un certo numero di attributi influisce sulla lingua in qualche modo. Gli attributi disponibili sono:
-
System.AttributeUsageAttribute(§23.5.2), usato per descrivere i modi in cui è possibile usare una classe di attributi. -
System.Diagnostics.ConditionalAttribute(§23.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(§23.5.4), utilizzato per contrassegnare un membro come obsoleto. -
System.Runtime.CompilerServices.AsyncMethodBuilderAttribute(§23.5.5), usato per stabilire un generatore di attività per un metodo asincrono. -
System.Runtime.CompilerServices.CallerLineNumberAttribute(§23.5.6.2),System.Runtime.CompilerServices.CallerFilePathAttribute(§23.5.6.3) eSystem.Runtime.CompilerServices.CallerMemberNameAttribute(§23.5.6.4), utilizzati per fornire informazioni sul contesto chiamante ai parametri facoltativi. -
System.Runtime.CompilerServices.EnumeratorCancellationAttribute(§23.5.8), usato per specificare il parametro per il token di annullamento in un iteratore asincrono.
Gli attributi di analisi statica nullable (§23.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#.
23.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 §23.2.2. nota finale
23.5.3 Attributo condizionale
23.5.3.1 Generale
L'attributo Conditional abilita la definizione dei metodi condizionalie della classe di attributi condizionalies.
23.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.Mdichiara come metodo condizionale associato ai due simboliALPHAdi compilazione condizionale eBETA.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
Conditionalviene specificato in un metodo in una dichiarazione di interfaccia. - Il metodo condizionale non deve essere una funzione di accesso di una proprietà, di un indicizzatore o di un evento.
- Il metodo condizionale deve avere un tipo restituito di
void. - Il metodo condizionale non deve essere contrassegnato con il
overridemodificatore. Un metodo condizionale può tuttavia essere contrassegnato con ilvirtualmodificatore. Le sostituzioni di tale metodo sono condizionali in modo implicito e non devono essere contrassegnate in modo esplicito con unConditionalattributo . - 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.
Nota: gli attributi con un
AttributeUsageoggetto (§23.2.2) possonoAttributeTargets.Methodessere normalmente applicati alle funzioni di accesso di proprietà, indicizzatori ed eventi. Le restrizioni precedenti impediscono questo utilizzo dell'attributoConditional. nota finale
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.Mdichiara come metodo condizionale.Class2Il metodo chiamaTestquesto metodo. Poiché il simboloDEBUGdi compilazione condizionale è definito, seClass2.Testviene chiamato , chiameràM. Se il simboloDEBUGnon fosse stato definito,Class2.Testnon chiamerebbeClass1.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
Class2classi eClass3ognuna contiene chiamate al metodoClass1.Fcondizionale , che è condizionale in base al fatto che sia definito o menoDEBUG. Poiché questo simbolo è definito nel contesto diClass2ma nonClass3, la chiamata aFinClass2è inclusa, mentre la chiamata a inFClass3viene 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 } }
Class2include una chiamata all'oggettoMdefinito nella relativa classe di base. Questa chiamata viene omessa perché il metodo di base è condizionale in base alla presenza del simboloDEBUG, 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
23.5.3.3 Classi di attributi condizionali
Una classe di attributi (§23.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 {}
TestAttributedichiara come classe di attributo condizionale associata ai simboliALPHAdi compilazione condizionale eBETA.esempio finale
Le specifiche degli attributi (§23.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
Class1eClass2sono ognuna decorata con l'attributoTest, che è condizionale in base al fatto che sia definito o menoDEBUG. Poiché questo simbolo è definito nel contesto diClass1ma nonClass2, viene inclusa la specifica dell'attributo Test inClass1, mentre la specifica dell'attributoTestsuClass2viene omessa.esempio finale
23.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, un compilatore genera un avviso o un errore. In particolare, un 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. Un compilatore genera un errore se viene specificato il parametro di errore 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'attributoObsolete. Ogni uso diAinMaingenera un avviso che include il messaggio specificato, "Questa classe è obsoleta; usare invece la classeB".esempio finale
23.5.5 L'attributo AsyncMethodBuilder
Questo attributo è descritto in §15.14.1.
23.5.6 Attributi caller-info
23.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.
23.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).
23.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).
23.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 (§23.6) nel 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.
23.5.7 Attributi di analisi codice
23.5.7.1 Generale
Gli attributi in questa sottosezione vengono usati per fornire informazioni aggiuntive per supportare un compilatore che fornisce nullabilità e diagnostica di 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 diagnostiche sullo stato nullo utilizza il significato definito in questa sottoclause per qualsiasi attributo tra questi che utilizza nelle proprie diagnosi.
Gli attributi di analisi del codice vengono dichiarati nello spazio dei nomi System.Diagnostics.CodeAnalysis.
| Attributo | significato |
|---|---|
AllowNull (§23.5.7.2) |
Un argomento non nullable può essere Null. |
DisallowNull (§23.5.7.3) |
Un argomento nullable non deve mai essere Null. |
MaybeNull (§23.5.7.6) |
Un valore restituito non nullable può essere Null. |
NotNull (§23.5.7.8) |
Un valore restituito nullable non sarà mai Null. |
MaybeNullWhen (§23.5.7.7) |
Un argomento non nullable può essere Null quando il metodo restituisce il valore specificato bool. |
NotNullWhen (§23.5.7.10) |
Un argomento nullable non sarà Null quando il metodo restituisce il valore specificato bool . |
NotNullIfNotNull (§23.5.7.9) |
Un valore restituito non è Null se l'argomento per il parametro specificato non è Null. |
DoesNotReturn (§23.5.7.4) |
Questo metodo non restituisce mai. |
DoesNotReturnIf (§23.5.7.5) |
Questo metodo non restituisce mai se il parametro associato bool ha il valore specificato. |
Le seguenti sottoclause in §23.5.7.1 sono normative condizionalmente.
23.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
nullmai 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 AllowNullsenza l'attributo, un compilatore può generare un avvertimento perché la proprietà di tipo non annullabile sembra essere impostata su un valore null. La presenza dell'attributo elimina l'avviso. esempio finale
23.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, pertanto un compilatore potrebbe 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
23.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 aiuta un compilatore in diversi modi. In primo luogo, un compilatore può generare un avviso se è presente un percorso in cui il metodo può uscire senza generare un'eccezione. In secondo luogo, un 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
23.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
23.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
Tviene sostituito dastring,T?diventa un'annotazione nullable. Tuttavia, questo codice non è valido perchéTnon è 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
23.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 (§23.5.7.6), ma include un parametro per il valore restituito specificato.
23.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 nullable sono abilitati, il metodo
ThrowWhenNullviene compilato senza avvisi. Quando termina, l'argomentovalueè garantito che nonnullsia . Tuttavia, è accettabile chiamareThrowWhenNullcon un riferimento Null. esempio finale
23.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 di un compilatore, quando un metodo restituisce sempre un valore non nullo se determinati argomenti non sono
null, è possibile usare l'attributoNotNullIfNotNull. Si consideri il modello seguente:#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }Se l'argomento
urlnonnullè ,nullnon 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
23.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 restituiscetruequando l'argomento ènullo una stringa vuota. Si tratta di una forma di controllo null: i chiamanti non devono controllare null-check l'argomento se il metodo restituiscefalse. 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
23.5.8 L'attributo EnumeratorCancellation
Specifica il parametro che rappresenta l'oggetto CancellationToken per un iteratore asincrono (§15.15). L'argomento per questo parametro deve essere combinato con l'argomento passato a IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken). Questo token combinato deve essere sottoposto a polling da IAsyncEnumerator<T>.MoveNextAsync() (§15.15.5.2). I token devono essere combinati in un singolo token come se fosse CancellationToken.CreateLinkedTokenSource e la relativa Token proprietà. Il token combinato verrà annullato se uno dei due token di origine viene annullato. Il token combinato viene considerato come l'argomento del metodo iteratore asincrono (§15.15) nel corpo di tale metodo.
Si tratta di un errore se l'attributo System.Runtime.CompilerServices.EnumeratorCancellation viene applicato a più di un parametro. Il compilatore può generare un avviso se:
- L'attributo
EnumeratorCancellationviene applicato a un parametro di un tipo diverso daCancellationToken, - o se l'attributo
EnumeratorCancellationviene applicato a un parametro su un metodo che non è un iteratore asincrono (§15.15), - o se l'attributo
EnumeratorCancellationviene applicato a un parametro su un metodo che restituisce un'interfaccia enumerabile asincrona (§15.15.3) anziché un'interfaccia enumeratore asincrona (§15.15.2).
L'iteratore non avrà accesso all'argomento CancellationToken per GetAsyncEnumerator quando non sono presenti attributi con questo parametro.
Esempio: il metodo
GetStringsAsync()è un iteratore asincrono. Prima di eseguire qualsiasi operazione per recuperare il valore successivo, controlla il token di annullamento per determinare se l'iterazione deve essere annullata. Se viene richiesto l'annullamento, non viene eseguita alcuna ulteriore azione.public static async Task ExampleCombination() { var sourceOne = new CancellationTokenSource(); var sourceTwo = new CancellationTokenSource(); await using (IAsyncEnumerator<string> enumerator = GetStringsAsync(sourceOne.Token).GetAsyncEnumerator(sourceTwo.Token)) { while (await enumerator.MoveNextAsync()) { string number = enumerator.Current; if (number == "8") sourceOne.Cancel(); if (number == "5") sourceTwo.Cancel(); Console.WriteLine(number); } } } static async IAsyncEnumerable<string> GetStringsAsync( [EnumeratorCancellation] CancellationToken token) { for (int i = 0; i < 10; i++) { if (token.IsCancellationRequested) yield break; await Task.Delay(1000, token); yield return i.ToString(); } }esempio finale
23.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
ECMA C# draft specification