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, public
protected
, internal
e 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.Attribute
astratta , 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'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, ilAttribute
suffisso può essere omesso, con il nomeSimple
breve . 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 AllowMultiple
indica 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'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 (§22.2.3), denominato , che Inherited
indica 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,url
e 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
,ulong
uint
long
sbyte
string
short
.ushort
double
char
bool
- 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 contenitoremodule
— la destinazione è il modulo contenitore
Non sono consentiti altri valori per global_attribute_target .
I nomi attribute_target standardizzati sono event
, , method
field
, param
, return
property
type
, , 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 delegatoreturn
— 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 metodoreturn
— 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'operatorereturn
— 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 associatoreturn
— 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 associatoparam
— 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 eventofield
— la destinazione è il campomethod
— 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 associatoparam
— 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 daSystem.Attribute
allora 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
Example
eExampleAttribute
. L'attributo[Example]
è ambiguo, poiché può fare riferimento aExample
oExampleAttribute
. 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 denominataExampleAttributeAttribute
!). Se la dichiarazione per la classeExample
viene 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
E
attributo (§22.2.4). - In fase di compilazione, il valore di
E
può 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.
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 T
di 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 costruttoreC
di istanza inT
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
inN
:- Si supponga di
Name
essere l'identificatore del named_argumentArg
. Name
identifica un campo o una proprietà pubblica di lettura non statica inT
. SeT
non 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
P
o uno dei valori all'interno di named_argument_listN
è di tipoSystem.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
T
dell'attributo , il costruttoreC
dell'istanza inT
, l'positional_argument_listP
, il named_argument_listN
e l'entitàE
programma 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
, P
C
, e N
e 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 costruttoreC
e i valori dell'istanza come determinato in fase di compilazione. Questi passaggi generano un'eccezione o producono un'istanzaO
diT
. - Per ogni named_argument
Arg
inN
, in ordine:- Si supponga di
Name
essere l'identificatore del named_argumentArg
. SeName
non identifica un campo di lettura/scrittura pubblico non statico o una proprietà inO
, viene generata un'eccezione. - Si supponga di
Value
valutare il attribute_argument_expression diArg
. - Se
Name
identifica 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 classeT
di attributi inizializzata con il positional_argument_listP
e l'named_argument_listN
.
- Si supponga di
Nota: il formato per l'archiviazione
T
di ,C
,P
,N
(e associarlo aE
) inA
e il meccanismo per specificareE
e recuperareT
, ,C
,P
N
daA
(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) eSystem.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 simboliALPHA
di 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
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 ilvirtual
modificatore. Le sostituzioni di tale metodo sono condizionali in modo implicito e non devono essere contrassegnate in modo esplicito con unConditional
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.Class2
Il metodo chiamaTest
questo metodo. Poiché il simboloDEBUG
di compilazione condizionale è definito, seClass2.Test
viene chiamato , chiameràM
. Se il simboloDEBUG
non fosse stato definito,Class2.Test
non 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
Class2
classi eClass3
ognuna contiene chiamate al metodoClass1.F
condizionale , che è condizionale in base al fatto che sia definito o menoDEBUG
. Poiché questo simbolo è definito nel contesto diClass2
ma nonClass3
, la chiamata aF
inClass2
è inclusa, mentre la chiamata a inF
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'oggettoM
definito 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
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 simboliALPHA
di compilazione condizionale eBETA
.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
eClass2
sono ognuna decorata con l'attributoTest
, che è condizionale in base al fatto che sia definito o menoDEBUG
. Poiché questo simbolo è definito nel contesto diClass1
ma nonClass2
, viene inclusa la specifica dell'attributo Test inClass1
, mentre la specifica dell'attributoTest
suClass2
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'attributoObsolete
. Ogni uso diA
inMain
genera un avviso che include il messaggio specificato, "Questa classe è obsoleta; usare invece la classeB
".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 dastring
,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'argomentovalue
è garantito che nonnull
sia . Tuttavia, è accettabile chiamareThrowWhenNull
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'attributoNotNullIfNotNull
può essere usato. Si consideri il modello seguente:#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }
Se l'argomento
url
nonnull
è ,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 restituiscetrue
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 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
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 è
TheItem
ora .esempio finale
ECMA C# draft specification