22 Attributs
22.1 Général
Une grande partie du langage C# permet au programmeur de spécifier des informations déclaratives sur les entités définies dans le programme. Par exemple, l’accessibilité d’une méthode dans une classe est spécifiée en la décorant avec les method_modifiers public
, protected
, internal
et private
.
C# permet aux programmeurs d’inventer de nouveaux types d’informations déclaratives, appelées attributs. Les programmeurs peuvent ensuite attacher des attributs à différentes entités de programme et récupérer des informations d’attribut dans un environnement d’exécution.
Remarque : Par exemple, une infrastructure peut définir un
HelpAttribute
attribut qui peut être placé sur certains éléments de programme (tels que des classes et des méthodes) pour fournir un mappage de ces éléments de programme à leur documentation. Note de fin
Les attributs sont définis par le biais de la déclaration de classes d’attributs (§22.2), qui peuvent avoir des paramètres positionnels et nommés (§22.2.3). Les attributs sont attachés aux entités d’un programme C# à l’aide de spécifications d’attributs (§22.3) et peuvent être récupérés au moment de l’exécution en tant qu’instances d’attribut (§22.4).
22.2 Classes d’attributs
22.2.1 Général
Une classe qui dérive de la classe System.Attribute
abstraite , directement ou indirectement, est une classe d’attributs. La déclaration d’une classe d’attribut définit un nouveau type d’attribut qui peut être placé sur des entités de programme. Par convention, les classes d’attributs sont nommées avec un suffixe de Attribute
. Les utilisations d’un attribut peuvent inclure ou omettre ce suffixe.
Une déclaration de classe générique ne doit pas être utilisée System.Attribute
comme classe de base directe ou indirecte.
Exemple :
public class B : Attribute {} public class C<T> : B {} // Error – generic cannot be an attribute
exemple de fin
22.2.2 Utilisation des attributs
L’attribut AttributeUsage
(§22.5.2) est utilisé pour décrire comment une classe d’attribut peut être utilisée.
AttributeUsage
a un paramètre positionnel (§22.2.3) qui permet à une classe d’attributs de spécifier les types d’entités de programme sur lesquelles il peut être utilisé.
Exemple : L’exemple suivant définit une classe d’attribut nommée
SimpleAttribute
qui peut être placée sur class_declarations et interface_declarations uniquement, et affiche plusieurs utilisations de l’attributSimple
.[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] public class SimpleAttribute : Attribute { ... } [Simple] class Class1 {...} [Simple] interface Interface1 {...}
Bien que cet attribut soit défini avec le nom
SimpleAttribute
, lorsque cet attribut est utilisé, leAttribute
suffixe peut être omis, ce qui entraîne le nomSimple
court. Par conséquent, l’exemple ci-dessus équivaut sémantiquement à ce qui suit :[SimpleAttribute] class Class1 {...} [SimpleAttribute] interface Interface1 {...}
exemple de fin
AttributeUsage
a un paramètre nommé (§22.2.3), appelé AllowMultiple
, qui indique si l’attribut peut être spécifié plusieurs fois pour une entité donnée. Si AllowMultiple
pour une classe d’attribut est true, cette classe d’attribut est une classe d’attributs multi-utilisation et peut être spécifiée plusieurs fois sur une entité. Si AllowMultiple
pour une classe d’attribut est false ou qu’elle n’est pas spécifiée, cette classe d’attribut est une classe d’attribut à usage unique et peut être spécifiée au maximum une fois sur une entité.
Exemple : L’exemple suivant définit une classe d’attributs multi-utilisation nommée
AuthorAttribute
et affiche une déclaration de classe avec deux utilisations de l’attributAuthor
:[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 { ... }
exemple de fin
AttributeUsage
a un autre paramètre nommé (§22.2.3), appelé Inherited
, qui indique si l’attribut, lorsqu’il est spécifié sur une classe de base, est également hérité par les classes qui dérivent de cette classe de base. Si Inherited
pour une classe d’attribut est true, cet attribut est hérité. Si Inherited
pour une classe d’attribut est false, cet attribut n’est pas hérité. S’il n’est pas spécifié, sa valeur par défaut est true.
Une classe X
d’attributs n’ayant pas d’attribut AttributeUsage
attaché à celui-ci, comme dans
class X : Attribute { ... }
équivaut à ce qui suit :
[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X : Attribute { ... }
22.2.3 Paramètres positionnels et nommés
Les classes d’attributs peuvent avoir des paramètrespositionnels et des paramètresnommés. Chaque constructeur d’instance publique pour une classe d’attribut définit une séquence valide de paramètres positionnels pour cette classe d’attributs. Chaque champ et propriété en lecture-écriture publique non statique pour une classe d’attribut définit un paramètre nommé pour la classe d’attributs. Pour qu’une propriété définisse un paramètre nommé, cette propriété doit avoir à la fois un accesseur get public et un accesseur de jeu public.
Exemple : L’exemple suivant définit une classe d’attributs nommée
HelpAttribute
qui a un paramètre positionnel,url
et un paramètre nommé,Topic
. Bien qu’elle ne soit pas statique et publique, la propriétéUrl
ne définit pas de paramètre nommé, car elle n’est pas en lecture-écriture. Deux utilisations de cet attribut sont également affichées :[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 { }
exemple de fin
22.2.4 Types de paramètres d’attribut
Les types de paramètres positionnels et nommés d’une classe d’attribut sont limités aux types de paramètres d’attribut, qui sont :
- L’un des types suivants :
bool
, ,byte
,float
.ulong
ushort
char
double
int
long
sbyte
short
string
uint
- Le type
object
. - Le type
System.Type
. - Les types enum.
- Tableaux unidimensionnels des types ci-dessus.
- Un argument de constructeur ou un champ public qui n’a pas l’un de ces types ne doit pas être utilisé comme paramètre positionnel ou nommé dans une spécification d’attribut.
22.3 Spécification d’attribut
La spécification d’attribut est l’application d’un attribut précédemment défini à une entité de programme. Un attribut est une partie d’informations déclaratives supplémentaires spécifiées pour une entité de programme. Les attributs peuvent être spécifiés à l’étendue globale (pour spécifier des attributs sur l’assembly ou module conteneur) et pour les type_declarations (§14.7), les class_member_declaration(§15.3), les interface_member_declaration(§15.3) §18.4), struct_member_declarations (§16.3), enum_member_declarations (§19.2), accessor_declaration(§15.7.3), event_accessor_ déclarations (§15.8), éléments de parameter_lists (§15.6.2) et éléments de type_parameter_lists (§15.2.3).
Les attributs sont spécifiés dans les sections d’attribut. Une section d’attribut se compose d’une paire de crochets, qui entourent une liste séparée par des virgules d’un ou plusieurs attributs. L’ordre dans lequel les attributs sont spécifiés dans une telle liste, et l’ordre dans lequel les sections attachées à la même entité de programme sont organisées, n’est pas significative. Par exemple, les spécifications d’attribut , , [B][A]
[A, B]
et [B, A]
sont équivalentes[A][B]
.
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
;
Pour la global_attribute_target de production, et dans le texte ci-dessous, l’identificateur doit avoir une orthographe égale ou assembly
module
, où l’égalité est définie dans le §6.4.3. Pour l’attribute_target de production, et dans le texte ci-dessous, l’identificateur doit avoir une orthographe qui n’est pas égale ou assembly
module
, en utilisant la même définition d’égalité que ci-dessus.
Un attribut se compose d’un attribute_name et d’une liste facultative d’arguments positionnels et nommés. Les arguments positionnels (s’il y en a) précèdent les arguments nommés. Un argument positionnel se compose d’un attribute_argument_expression ; un argument nommé se compose d’un nom, suivi d’un signe égal, suivi d’un attribute_argument_expression, qui, ensemble, sont limités par les mêmes règles que l’affectation simple. L’ordre des arguments nommés n’est pas significatif.
Remarque : Pour des raisons pratiques, une virgule de fin est autorisée dans un global_attribute_section et un attribute_section, tout comme dans un array_initializer (§17.7). Note de fin
La attribute_name identifie une classe d’attributs.
Lorsqu’un attribut est placé au niveau global, une global_attribute_target_specifier est requise. Lorsque la global_attribute_target est égale à :
assembly
— la cible est l’assembly conteneurmodule
— la cible est le module conteneur
Aucune autre valeur pour global_attribute_target n’est autorisée.
Les noms de attribute_target standardisés sont , , field
param
method
, property
, return
, , , type
et typevar
.event
Ces noms cibles ne doivent être utilisés que dans les contextes suivants :
event
— un événement.field
— un champ. Un événement de type champ (c’est-à-dire un sans accesseurs) (§15.8.2) et une propriété implémentée automatiquement (§15.7.4) peut également avoir un attribut avec cette cible.method
— constructeur, finaliseur, méthode, opérateur, propriété get and set accessors, indexer get and set accessors, and event add and remove accessors. Un événement de type champ (c’est-à-dire sans accesseurs) peut également avoir un attribut avec cette cible.param
— un accesseur de jeu de propriétés, un accesseur de jeu d’indexeurs, des accesseurs d’ajout et de suppression d’événements et un paramètre dans un constructeur, une méthode et un opérateur.property
— une propriété et un indexeur.return
— un délégué, une méthode, un opérateur, un accesseur get de propriété et un accesseur get de l’indexeur.type
— un délégué, une classe, un struct, une énumération et une interface.typevar
— paramètre de type.
Certains contextes permettent la spécification d’un attribut sur plusieurs cibles. Un programme peut spécifier explicitement la cible en incluant un attribute_target_specifier. Sans attribute_target_specifier une valeur par défaut est appliquée, mais une attribute_target_specifier peut être utilisée pour affirmer ou remplacer la valeur par défaut. Les contextes sont résolus comme suit :
- Pour un attribut sur une déclaration de délégué, la cible par défaut est le délégué. Sinon, lorsque la attribute_target est égale à :
type
— la cible est le déléguéreturn
— la cible est la valeur de retour
- Pour un attribut sur une déclaration de méthode, la cible par défaut est la méthode. Sinon, lorsque la attribute_target est égale à :
method
— la cible est la méthodereturn
— la cible est la valeur de retour
- Pour un attribut sur une déclaration d’opérateur, la cible par défaut est l’opérateur. Sinon, lorsque la attribute_target est égale à :
method
— la cible est l’opérateurreturn
— la cible est la valeur de retour
- Pour un attribut sur une déclaration d’accesseur get pour une déclaration de propriété ou d’indexeur, la cible par défaut est la méthode associée. Sinon, lorsque la attribute_target est égale à :
method
— la cible est la méthode associéereturn
— la cible est la valeur de retour
- Pour un attribut spécifié sur un accesseur set pour une déclaration de propriété ou d’indexeur, la cible par défaut est la méthode associée. Sinon, lorsque la attribute_target est égale à :
method
— la cible est la méthode associéeparam
— la cible est le paramètre implicite seul
- Pour un attribut sur une déclaration de propriété implémentée automatiquement, la cible par défaut est la propriété. Sinon, lorsque la attribute_target est égale à :
field
— la cible est le champ de stockage généré par le compilateur pour la propriété
- Pour un attribut spécifié sur une déclaration d’événement qui omet event_accessor_declarations la cible par défaut est la déclaration d’événement. Sinon, lorsque la attribute_target est égale à :
event
— la cible est la déclaration d’événementfield
— la cible est le champmethod
— les cibles sont les méthodes
- Dans le cas d’une déclaration d’événement qui n’omet pas event_accessor_declarations la cible par défaut est la méthode.
method
— la cible est la méthode associéeparam
— la cible est le paramètre solitaire
Dans tous les autres contextes, l’inclusion d’une attribute_target_specifier est autorisée mais inutile.
Exemple : une déclaration de classe peut inclure ou omettre le spécificateur
type
:[type: Author("Brian Kernighan")] class Class1 {} [Author("Dennis Ritchie")] class Class2 {}
exemple de fin.
Une implémentation peut accepter d’autres attribute_targets, dont les objectifs sont définis par l’implémentation. Une implémentation qui ne reconnaît pas une telle attribute_target émet un avertissement et ignore le attribute_section contenant.
Par convention, les classes d’attributs sont nommées avec un suffixe de Attribute
. Une attribute_name peut inclure ou omettre ce suffixe. Plus précisément, une attribute_name est résolue comme suit :
- Si l’identificateur le plus à droite de l’attribute_name est un identificateur détaillé (§6.4.3), le attribute_name est résolu en tant que type_name (§7.8). Si le résultat n’est pas un type dérivé de , une erreur au moment de
System.Attribute
la compilation se produit. - Sinon,
- Le attribute_name est résolu sous la forme d’un type_name (§7.8), sauf si des erreurs sont supprimées. Si cette résolution réussit et génère un type dérivé de
System.Attribute
cette étape, le type est le résultat de cette étape. - Les caractères
Attribute
sont ajoutés à l’identificateur le plus à droite dans l’attribute_name et la chaîne de jetons résultante est résolue en tant que type_name (§7.8), sauf toute erreur est supprimée. Si cette résolution réussit et génère un type dérivé deSystem.Attribute
cette étape, le type est le résultat de cette étape.
- Le attribute_name est résolu sous la forme d’un type_name (§7.8), sauf si des erreurs sont supprimées. Si cette résolution réussit et génère un type dérivé de
Si exactement l’une des deux étapes ci-dessus entraîne un type dérivé System.Attribute
de , ce type est le résultat de la attribute_name. Sinon, une erreur au moment de la compilation se produit.
Exemple : si une classe d’attributs est trouvée avec et sans ce suffixe, une ambiguïté est présente et un résultat d’erreur au moment de la compilation. Si l’attribute_name est orthographié de telle sorte que son identificateur le plus à droite soit un identificateur détaillé (§6.4.3), seul un attribut sans suffixe est mis en correspondance, ce qui permet de résoudre une telle ambiguïté. L’exemple
[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 {}
affiche deux classes d’attribut nommées
Example
etExampleAttribute
. L’attribut[Example]
est ambigu, car il peut faire référence à l’un ouExampleAttribute
l’autreExample
. L’utilisation d’un identificateur détaillé permet de spécifier l’intention exacte dans de tels cas rares. L’attribut[ExampleAttribute]
n’est pas ambigu (bien qu’il soit s’il y avait une classe d’attribut nomméeExampleAttributeAttribute
!). Si la déclaration de la classeExample
est supprimée, les deux attributs font référence à la classe d’attribut nomméeExampleAttribute
, comme suit :[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 {}
exemple de fin
Il s’agit d’une erreur au moment de la compilation pour utiliser une classe d’attributs à usage unique plusieurs fois sur la même entité.
Exemple : l’exemple
[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 {}
génère une erreur au moment de la compilation, car elle tente d’utiliser
HelpString
, qui est une classe d’attribut à usage unique, plusieurs fois sur la déclaration deClass1
.exemple de fin
Une expression E
est une attribute_argument_expression si toutes les instructions suivantes sont vraies :
- Le type de paramètre est un type de
E
paramètre d’attribut (§22.2.4). - Au moment de la compilation, la valeur de
E
peut être résolue en l’une des options suivantes :
Exemple :
[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; }
exemple de fin
Les attributs d’un type déclaré dans plusieurs parties sont déterminés en combinant, dans un ordre non spécifié, les attributs de chacune de ses parties. Si le même attribut est placé sur plusieurs parties, il équivaut à spécifier cet attribut plusieurs fois sur le type.
Exemple : les deux parties :
[Attr1, Attr2("hello")] partial class A {} [Attr3, Attr2("goodbye")] partial class A {}
sont équivalents à la déclaration unique suivante :
[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")] class A {}
exemple de fin
Les attributs sur les paramètres de type se combinent de la même façon.
22.4 Instances d’attribut
22.4.1 Général
Une instance d’attribut est une instance qui représente un attribut au moment de l’exécution. Un attribut est défini avec une classe d’attributs, des arguments positionnels et des arguments nommés. Une instance d’attribut est une instance de la classe d’attribut initialisée avec les arguments positionnels et nommés.
La récupération d’une instance d’attribut implique à la fois le traitement au moment de la compilation et au moment de l’exécution, comme décrit dans les sous-sections suivantes.
22.4.2 Compilation d’un attribut
La compilation d’un attribut avec une classe T
d’attributs, positional_argument_listP
, named_argument_listN
et spécifiée sur une entité E
de programme est compilée dans un assembly A
en procédant comme suit :
- Suivez les étapes de traitement au moment de la compilation d’un object_creation_expression du formulaire nouveau
T(P)
. Ces étapes entraînent une erreur au moment de la compilation ou déterminent un constructeurC
d’instance surT
lequel il peut être appelé au moment de l’exécution. - S’il
C
n’a pas d’accessibilité publique, une erreur au moment de la compilation se produit. - Pour chaque named_argument
Arg
dansN
:- Supposons
Name
que l’identificateur de la named_argumentArg
. Name
doit identifier un champ public ou une propriété en lecture-écriture non statique surT
. S’ilT
n’existe pas de champ ou de propriété de ce type, une erreur au moment de la compilation se produit.
- Supposons
- Si l’une des valeurs dans positional_argument_list
P
ou l’une des valeurs de named_argument_listN
est de typeSystem.String
et que la valeur n’est pas bien formée telle que définie par la norme Unicode, elle est définie par l’implémentation si la valeur compilée est égale à la valeur d’exécution récupérée (§22.4.3).Remarque : Par exemple, une chaîne qui contient une unité de code UTF-16 de substitution élevée qui n’est pas immédiatement suivie d’une unité de code de substitution faible n’est pas bien formée. Note de fin
- Stockez les informations suivantes (pour l’instanciation au moment de l’exécution de l’attribut) dans la sortie de l’assembly par le compilateur en raison de la compilation du programme contenant l’attribut : la classe
T
d’attribut, le constructeurC
d’instance surT
, le positional_argument_listP
, le named_argument_listN
et l’entitéE
de programme associée, avec les valeurs résolues complètement au moment de la compilation.
22.4.3 Récupération au moment de l’exécution d’une instance d’attribut
À l’aide des termes définis dans le §22.4.2, l’instance d’attribut représentée par T
, C
P
et N
associée E
à celle-ci peut être récupérée au moment de l’exécution à partir de l’assembly A
en procédant comme suit :
- Suivez les étapes de traitement au moment de l’exécution d’un object_creation_expression du formulaire
new T(P)
, en utilisant le constructeurC
d’instance et les valeurs comme déterminé au moment de la compilation. Ces étapes entraînent une exception ou produisent une instanceO
deT
. - Pour chaque named_argument
Arg
dansN
, dans l’ordre :- Supposons
Name
que l’identificateur de la named_argumentArg
. SiName
elle n’identifie pas un champ ou une propriété en lecture-écriture publique non statique surO
, une exception est levée. - Nous allons
Value
être le résultat de l’évaluation de la attribute_argument_expression deArg
. - Si
Name
elle identifie un champ surO
, définissez ce champ surValue
. - Sinon, Name identifie une propriété sur
O
. Définissez cette propriété sur Value. - Le résultat est
O
, une instance de la classeT
d’attributs qui a été initialisée avec le positional_argument_listP
et le named_argument_listN
.
- Supposons
Remarque : Le format de stockage
T
, ,C
P
(N
et l’associant àE
) dansA
et le mécanisme permettant de spécifierE
et de récupérerT
,P
C
N
à partirA
(et donc de la façon dont une instance d’attribut est obtenue au moment de l’exécution) dépasse l’étendue de cette spécification. Note de fin
Exemple : Dans une implémentation de l’interface CLI, les
Help
instances d’attributs de l’assembly créées en compilant l’exemple de programme dans §22.2.3 peuvent être récupérées avec le programme suivant :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}"); } } } }
exemple de fin
22.5 Attributs réservés
22.5.1 Général
Un certain nombre d’attributs affectent la langue d’une certaine manière. Ces attributs incluent :
System.AttributeUsageAttribute
(§22.5.2), qui est utilisé pour décrire les façons dont une classe d’attribut peut être utilisée.System.Diagnostics.ConditionalAttribute
(§22.5.3), est une classe d’attributs multi-utilisation utilisée pour définir des méthodes conditionnelles et des classes d’attributs conditionnels. Cet attribut indique une condition en testant un symbole de compilation conditionnelle.System.ObsoleteAttribute
(§22.5.4), utilisé pour marquer un membre comme obsolète.System.Runtime.CompilerServices.AsyncMethodBuilderAttribute
(§22.5.5), utilisé pour établir un générateur de tâches pour une méthode asynchrone.System.Runtime.CompilerServices.CallerLineNumberAttribute
(§22.5.6.2),System.Runtime.CompilerServices.CallerFilePathAttribute
(§22.5.6.3) etSystem.Runtime.CompilerServices.CallerMemberNameAttribute
(§22.5.6.4), qui sont utilisés pour fournir des informations sur le contexte appelant aux paramètres facultatifs.
Les attributs d’analyse statique nullable (§22.5.7) peuvent améliorer la correction des avertissements générés pour les capacités Null et les états Null (§8.9.5).
Un environnement d’exécution peut fournir des attributs définis par l’implémentation supplémentaires qui affectent l’exécution d’un programme C#.
22.5.2 Attributs
L’attribut AttributeUsage
est utilisé pour décrire la façon dont la classe d’attributs peut être utilisée.
Une classe décorée avec l’attribut AttributeUsage
dérive directement ou indirectement de System.Attribute
l’attribut. Sinon, une erreur au moment de la compilation se produit.
Remarque : Pour obtenir un exemple d’utilisation de cet attribut, consultez §22.2.2. Note de fin
22.5.3 L’attribut conditionnel
22.5.3.1 Général
L’attribut Conditional
active la définition des méthodes conditionnelles et des classes d’attributs conditionnels.
22.5.3.2 Méthodes conditionnelles
Une méthode décorée avec l’attribut Conditional
est une méthode conditionnelle. Chaque méthode conditionnelle est donc associée aux symboles de compilation conditionnelle déclarés dans ses Conditional
attributs.
Exemple :
class Eg { [Conditional("ALPHA")] [Conditional("BETA")] public static void M() { // ... } }
déclare
Eg.M
en tant que méthode conditionnelle associée aux deux symbolesALPHA
de compilation conditionnelle etBETA
.exemple de fin
Un appel à une méthode conditionnelle est inclus si un ou plusieurs de ses symboles de compilation conditionnelle associés sont définis au point d’appel, sinon l’appel est omis.
Une méthode conditionnelle est soumise aux restrictions suivantes :
- La méthode conditionnelle doit être une méthode dans un class_declaration ou struct_declaration. Une erreur au moment de la compilation se produit si l’attribut
Conditional
est spécifié sur une méthode dans une déclaration d’interface. - La méthode conditionnelle doit avoir un type de retour .
void
- La méthode conditionnelle ne doit pas être marquée avec le
override
modificateur. Toutefois, une méthode conditionnelle peut être marquée avec levirtual
modificateur. Les remplacements de cette méthode sont implicitement conditionnels et ne doivent pas être explicitement marqués avec unConditional
attribut. - La méthode conditionnelle ne doit pas être une implémentation d’une méthode d’interface. Sinon, une erreur au moment de la compilation se produit.
- Les paramètres de la méthode conditionnelle ne doivent pas être des paramètres de sortie.
En outre, une erreur au moment de la compilation se produit si un délégué est créé à partir d’une méthode conditionnelle.
Exemple : l’exemple
#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(); } }
déclare
Class1.M
comme méthode conditionnelle.Class2
laTest
méthode appelle cette méthode. Étant donné que le symboleDEBUG
de compilation conditionnelle est défini, s’ilClass2.Test
est appelé, il appelleM
. Si le symboleDEBUG
n’avait pas été défini, n’appelaitClass2.Test
Class1.M
pas .exemple de fin
Il est important de comprendre que l’inclusion ou l’exclusion d’un appel à une méthode conditionnelle est contrôlée par les symboles de compilation conditionnelle au point de l’appel.
Exemple : dans le code suivant
// 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 } }
les classes
Class2
etClass3
chacune contiennent des appels à la méthodeClass1.F
conditionnelle, qui est conditionnelle selon qu’elle est définie ou nonDEBUG
. Étant donné que ce symbole est défini dans le contexte deClass2
mais pasClass3
, l’appel àF
dansClass2
est inclus, tandis que l’appel àF
l’entréeClass3
est omis.exemple de fin
L’utilisation de méthodes conditionnelles dans une chaîne d’héritage peut prêter à confusion. Les appels effectués vers une méthode conditionnelle via base
, du formulaire base.M
, sont soumis aux règles d’appel de méthode conditionnelle normales.
Exemple : dans le code suivant
// 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
inclut un appel à l’élémentM
défini dans sa classe de base. Cet appel est omis, car la méthode de base est conditionnelle en fonction de la présence du symboleDEBUG
, qui n’est pas définie. Ainsi, la méthode écrit uniquement dans la console «Class2.M executed
». L’utilisation judicieuse de pp_declarationpeut éliminer de tels problèmes.exemple de fin
22.5.3.3 Classes d’attributs conditionnels
Une classe d’attributs (§22.2) décorée avec un ou plusieurs Conditional
attributs est une classe d’attributs conditionnelle. Une classe d’attributs conditionnels est donc associée aux symboles de compilation conditionnelle déclarés dans ses Conditional
attributs.
Exemple :
[Conditional("ALPHA")] [Conditional("BETA")] public class TestAttribute : Attribute {}
déclare
TestAttribute
en tant que classe d’attribut conditionnel associée aux symboles de compilation conditionnelleALPHA
etBETA
.exemple de fin
Les spécifications d’attribut (§22.3) d’un attribut conditionnel sont incluses si un ou plusieurs de ses symboles de compilation conditionnelle associés sont définis au point de spécification, sinon la spécification d’attribut est omise.
Il est important de noter que l’inclusion ou l’exclusion d’une spécification d’attribut d’une classe d’attributs conditionnels est contrôlée par les symboles de compilation conditionnelle au point de la spécification.
Exemple : Dans l’exemple
// 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 {}
les classes
Class1
etClass2
sont chacune décorées avec un attributTest
, qui est conditionnelle selon qu’elle est définie ou nonDEBUG
. Dans la mesure où ce symbole est défini dans le contexte deClass1
l’attribut Test activé, mais pasClass2
dans le contexte, la spécification de l’attribut estClass1
incluse, tandis que la spécification de l’attributTest
estClass2
omise.exemple de fin
22.5.4 Attribut obsolète
L’attribut Obsolete
est utilisé pour marquer les types et les membres des types qui ne doivent plus être utilisés.
Si un programme utilise un type ou un membre décoré avec l’attribut Obsolete
, le compilateur émet un avertissement ou une erreur. Plus précisément, le compilateur émet un avertissement si aucun paramètre d’erreur n’est fourni, ou si le paramètre d’erreur est fourni et a la valeur false
. Le compilateur émet une erreur si le paramètre d’erreur est spécifié et a la valeur true
.
Exemple : dans le code suivant
[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
est décorée avec l’attributObsolete
. Chaque utilisation deA
résultatsMain
dans un avertissement qui inclut le message spécifié, « Cette classe est obsolète ; utiliser la classeB
à la place ».exemple de fin
22.5.5 L’attribut AsyncMethodBuilder
Cet attribut est décrit dans le §15.15.1.
22.5.6 Attributs d’informations de l’appelant
22.5.6.1 Général
À des fins telles que la journalisation et la création de rapports, il est parfois utile pour un membre de fonction d’obtenir certaines informations au moment de la compilation sur le code appelant. Les attributs d’informations de l’appelant permettent de transmettre ces informations de manière transparente.
Lorsqu’un paramètre facultatif est annoté avec l’un des attributs caller-info, l’omission de l’argument correspondant dans un appel n’entraîne pas nécessairement la substitution de la valeur de paramètre par défaut. Au lieu de cela, si les informations spécifiées sur le contexte appelant sont disponibles, ces informations seront transmises en tant que valeur d’argument.
Exemple :
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); }
Un appel à
Log()
sans arguments imprime le numéro de ligne et le chemin d’accès du fichier de l’appel, ainsi que le nom du membre dans lequel l’appel s’est produit.exemple de fin
Les attributs d’informations de l’appelant peuvent se produire sur des paramètres facultatifs n’importe où, y compris dans les déclarations de délégué. Toutefois, les attributs d’informations de l’appelant spécifique ont des restrictions sur les types des paramètres qu’ils peuvent attribuer, afin qu’il y ait toujours une conversion implicite d’une valeur substituée au type de paramètre.
Il s’agit d’une erreur d’avoir le même attribut d’info-appelant sur un paramètre de la définition et de l’implémentation d’une partie d’une déclaration de méthode partielle. Seuls les attributs d’informations de l’appelant dans la partie de définition sont appliqués, tandis que les attributs d’informations de l’appelant qui se produisent uniquement dans le composant d’implémentation sont ignorés.
Les informations de l’appelant n’affectent pas la résolution de surcharge. Étant donné que les paramètres facultatifs attribués sont toujours omis à partir du code source de l’appelant, la résolution de surcharge ignore ces paramètres de la même façon qu’il ignore les autres paramètres facultatifs omis (§12.6.4).
Les informations de l’appelant sont remplacées uniquement lorsqu’une fonction est explicitement appelée dans le code source. Les appels implicites tels que les appels de constructeur parent implicites n’ont pas d’emplacement source et ne remplacent pas les informations de l’appelant. En outre, les appels liés dynamiquement ne remplacent pas les informations de l’appelant. Lorsqu’un paramètre attribut d’info-appelant est omis dans de tels cas, la valeur par défaut spécifiée du paramètre est utilisée à la place.
Une exception est les expressions de requête. Ces extensions sont considérées comme syntactiques et, si les appels qu’ils développent pour omettre des paramètres facultatifs avec des attributs d’informations d’appelant, les informations de l’appelant seront remplacées. L’emplacement utilisé est l’emplacement de la clause de requête à partir de laquelle l’appel a été généré.
Si plusieurs attributs caller-info sont spécifiés sur un paramètre donné, ils sont reconnus dans l’ordre suivant : CallerLineNumber
, CallerFilePath
. CallerMemberName
Considérez la déclaration de paramètre suivante :
[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...
CallerLineNumber
est prioritaire, et les deux autres attributs sont ignorés. S’il CallerLineNumber
était omis, CallerFilePath
précédait et CallerMemberName
serait ignoré. L’ordre lexical de ces attributs n’est pas pertinent.
22.5.6.2 Attribut CallerLineNumber
L’attribut System.Runtime.CompilerServices.CallerLineNumberAttribute
est autorisé sur les paramètres facultatifs lorsqu’il existe une conversion implicite standard (§10.4.2) de la valeur int.MaxValue
constante au type du paramètre. Cela garantit que tout numéro de ligne non négatif jusqu’à cette valeur peut être transmis sans erreur.
Si un appel de fonction à partir d’un emplacement dans le code source omet un paramètre facultatif avec le CallerLineNumberAttribute
, un littéral numérique représentant le numéro de ligne de cet emplacement est utilisé comme argument pour l’appel au lieu de la valeur de paramètre par défaut.
Si l’appel s’étend sur plusieurs lignes, la ligne choisie dépend de l’implémentation.
Le numéro de ligne peut être affecté par #line
les directives (§6.5.8).
22.5.6.3 L’attribut CallerFilePath
L’attribut System.Runtime.CompilerServices.CallerFilePathAttribute
est autorisé sur les paramètres facultatifs lorsqu’il existe une conversion implicite standard (§10.4.2) du string
type du paramètre.
Si un appel de fonction à partir d’un emplacement dans le code source omet un paramètre facultatif avec le CallerFilePathAttribute
, un littéral de chaîne représentant le chemin d’accès du fichier de cet emplacement est utilisé comme argument à l’appel au lieu de la valeur de paramètre par défaut.
Le format du chemin d’accès au fichier dépend de l’implémentation.
Le chemin d’accès au fichier peut être affecté par #line
les directives (§6.5.8).
22.5.6.4 Attribut CallerMemberName
L’attribut System.Runtime.CompilerServices.CallerMemberNameAttribute
est autorisé sur les paramètres facultatifs lorsqu’il existe une conversion implicite standard (§10.4.2) du string
type du paramètre.
Si un appel de fonction à partir d’un emplacement dans le corps d’un membre de fonction ou dans un attribut appliqué au membre de la fonction lui-même ou à son type de retour, paramètres ou paramètres de type dans le code source omet un paramètre facultatif avec le CallerMemberNameAttribute
, un littéral de chaîne représentant le nom de ce membre est utilisé comme argument de l’appel au lieu de la valeur de paramètre par défaut.
Pour les appels qui se produisent dans les méthodes génériques, seul le nom de la méthode lui-même est utilisé, sans la liste de paramètres de type.
Pour les appels qui se produisent dans des implémentations de membres d’interface explicites, seul le nom de méthode lui-même est utilisé, sans la qualification de l’interface précédente.
Pour les appels qui se produisent dans les accesseurs de propriété ou d’événement, le nom de membre utilisé est celui de la propriété ou de l’événement lui-même.
Pour les appels qui se produisent dans les accesseurs d’indexeur, le nom de membre utilisé est celui fourni par un IndexerNameAttribute
(§22.6) sur le membre de l’indexeur, le cas échéant ou le nom Item
par défaut.
Pour les appels qui se produisent dans les initialiseurs de champ ou d’événement, le nom de membre utilisé est le nom du champ ou de l’événement initialisé.
Pour les appels qui se produisent dans les déclarations des constructeurs d’instance, des constructeurs statiques, des finaliseurs et des opérateurs utilisés, le nom de membre utilisé dépend de l’implémentation.
22.5.7 Attributs d’analyse du code
22.5.7.1 Général
Les attributs de cette section sont utilisés pour fournir des informations supplémentaires pour prendre en charge un compilateur qui fournit des diagnostics nullabilité et null-state (§8.9.5). Un compilateur n’est pas nécessaire pour effectuer des diagnostics d’état Null. La présence ou l’absence de ces attributs n’affectent pas la langue ni le comportement d’un programme. Un compilateur qui ne fournit pas de diagnostics d’état null doit lire et ignorer la présence de ces attributs. Un compilateur qui fournit des diagnostics d’état null doit utiliser la signification définie dans cette section pour l’un de ces attributs qu’il utilise pour informer ses diagnostics.
Les attributs d’analyse du code sont déclarés dans l’espace de noms System.Diagnostics.CodeAnalysis
.
Attribut | Signification |
---|---|
AllowNull (§22.5.7.2) |
Un argument non nullable peut être null. |
DisallowNull (§22.5.7.3) |
Un argument nullable ne doit jamais être null. |
MaybeNull (§22.5.7.6) |
Une valeur de retour non nullable peut être null. |
NotNull (§22.5.7.8) |
Une valeur de retour nullable ne sera jamais null. |
MaybeNullWhen (§22.5.7.7) |
Un argument non-nullable peut être null quand la méthode retourne la valeur bool spécifiée. |
NotNullWhen (§22.5.7.10) |
Un argument nullable ne sera pas null lorsque la méthode retourne la valeur spécifiée bool . |
NotNullIfNotNull (§22.5.7.9) |
Une valeur de retour n’est pas null si l’argument du paramètre spécifié n’est pas null. |
DoesNotReturn (§22.5.7.4) |
Cette méthode ne retourne jamais. |
DoesNotReturnIf (§22.5.7.5) |
Cette méthode ne retourne jamais si le paramètre associé bool a la valeur spécifiée. |
Les sections suivantes de l’article 22.5.7.1 sont conditionnellement normatives.
22.5.7.2 Attribut AllowNull
Spécifie qu’une valeur Null est autorisée en tant qu’entrée même si le type correspondant l’interdit.
Exemple : considérez la propriété en lecture/écriture suivante qui ne retourne
null
jamais, car elle a une valeur par défaut raisonnable. Toutefois, un utilisateur peut donner la valeur Null au accesseur set pour définir la propriété sur cette valeur par défaut.#nullable enable public class X { [AllowNull] public string ScreenName { get => _screenName; set => _screenName = value ?? GenerateRandomScreenName(); } private string _screenName = GenerateRandomScreenName(); private static string GenerateRandomScreenName() => ...; }
Compte tenu de l’utilisation suivante de l’accesseur set de cette propriété
var v = new X(); v.ScreenName = null; // may warn without attribute AllowNull
sans l’attribut, le compilateur peut générer un avertissement, car la propriété non nullable-typée semble être définie sur une valeur Null. La présence de l’attribut supprime cet avertissement. exemple de fin
22.5.7.3 L’attribut DisallowNull
Spécifie qu’une valeur null est interdite en tant qu’entrée même si le type correspondant l’autorise.
Exemple : considérez la propriété suivante dans laquelle la valeur null est la valeur par défaut, mais les clients peuvent uniquement la définir sur une valeur 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; }
L’accesseur get peut retourner la valeur par défaut de
null
, de sorte que le compilateur peut avertir qu’il doit être vérifié avant l’accès. En outre, il avertit les appelants que, même s’il peut s’agir de null, les appelants ne doivent pas le définir explicitement sur Null. exemple de fin
22.5.7.4 L’attribut DoesNotReturn
Spécifie qu’une méthode donnée ne retourne jamais.
Exemple : Tenez compte des éléments suivants :
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 présence de l’attribut aide le compilateur de plusieurs façons. Tout d’abord, le compilateur peut émettre un avertissement s’il existe un chemin d’accès où la méthode peut quitter sans lever d’exception. Deuxièmement, le compilateur peut supprimer des avertissements nullables dans n’importe quel code après un appel à cette méthode, jusqu’à ce qu’une clause catch appropriée soit trouvée. Troisièmement, le code inaccessible n’affecte aucun état Null.
L’attribut ne change pas l’accessibilité (§13.2) ou l’analyse de l’affectation définie (§9.4) en fonction de la présence de cet attribut. Il est utilisé uniquement pour affecter les avertissements de nullabilité. exemple de fin
22.5.7.5 L’attribut DoesNotReturnIf
Spécifie qu’une méthode donnée ne retourne jamais si le paramètre associé bool
a la valeur spécifiée.
Exemple : Tenez compte des éléments suivants :
#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!; }
exemple de fin
22.5.7.6 L’attribut MaybeNull
Spécifie qu’une valeur de retour non nullable peut être null.
Exemple : Considérez la méthode générique suivante :
#nullable enable public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
L’idée de ce code est que si
T
elle est remplacée parstring
,T?
devient une annotation nullable. Toutefois, ce code n’est pas légal, carT
il n’est pas contraint d’être un type de référence. Toutefois, l’ajout de cet attribut résout le problème :#nullable enable [return: MaybeNull] public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
L’attribut informe les appelants que le contrat implique un type non nullable, mais que la valeur de retour peut réellement être
null
. exemple de fin
22.5.7.7 L’attribut MaybeNullWhen
Spécifie qu’un argument non nullable peut être null
lorsque la méthode retourne la valeur spécifiée bool
. Ceci est similaire à l’attribut MaybeNull
(§22.5.7.6), mais inclut un paramètre pour la valeur de retour spécifiée.
22.5.7.8 Attribut NotNull
Spécifie qu’une valeur nullable ne sera null
jamais si la méthode retourne (au lieu de lever).
Exemple : Tenez compte des éléments suivants :
#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); }
Lorsque les types de référence Null sont activés, la méthode
ThrowWhenNull
se compile sans avertissements. Lorsque cette méthode est retournée, l’argumentvalue
est garanti ne pasnull
être . Toutefois, il est acceptable d’appelerThrowWhenNull
avec une référence Null. exemple de fin
22.5.7.9 Attribut NotNullIfNotNull
Spécifie qu’une valeur de retour n’est pas null
si l’argument du paramètre spécifié n’est pas null
.
Exemple : L’état null d’une valeur de retour peut dépendre de l’état Null d’un ou de plusieurs arguments. Pour faciliter l’analyse du compilateur lorsqu’une méthode retourne toujours une valeur non null lorsque certains arguments ne sont pas
null
l’attributNotNullIfNotNull
peut être utilisé. Considérez la méthode suivante :#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }
Si l’argument
url
n’est pasnull
,null
n’est pas retourné. Lorsque des références nullables sont activées, cette signature fonctionne correctement, à condition que l’API n’accepte jamais d’argument Null. Toutefois, si l’argument peut être null, la valeur de retour peut également être null. Pour exprimer ce contrat correctement, annotez cette méthode comme suit :#nullable enable [return: NotNullIfNotNull("url")] string? GetTopLevelDomainFromFullUrl(string? url) { ... }
exemple de fin
22.5.7.10 Attribut NotNullWhen
Spécifie qu’un argument nullable ne sera null
pas lorsque la méthode retourne la valeur spécifiée bool
.
Exemple : la méthode
String.IsNullOrEmpty(String)
de bibliothèque retournetrue
lorsque l’argument estnull
ou une chaîne vide. Il s’agit d’une forme de vérification null : les appelants n’ont pas besoin de vérifier null-check l’argument si la méthode retournefalse
. Pour faire en sorte qu’une méthode comme celle-ci prenne en compte les valeurs Null, faites en sorte que le type de paramètre soit un type de référence nullable et ajoutez l’attribut NotNullWhen :#nullable enable bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }
exemple de fin
22.6 Attributs pour l’interopérabilité
Pour l’interopérabilité avec d’autres langages, un indexeur peut être implémenté à l’aide de propriétés indexées. Si aucun attribut n’est IndexerName
présent pour un indexeur, le nom Item
est utilisé par défaut. L’attribut IndexerName
permet à un développeur de remplacer cette valeur par défaut et de spécifier un autre nom.
Exemple : par défaut, le nom d’un indexeur est
Item
. Cela peut être substitué, comme suit :[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { get { ... } set { ... } }
Maintenant, le nom de l’indexeur est
TheItem
.exemple de fin
ECMA C# draft specification