Partager via


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, internalet 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.Attributeabstraite , 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’attribut Simple .

[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é, le Attribute suffixe peut être omis, ce qui entraîne le nom Simplecourt. 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’attribut Author :

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class AuthorAttribute : Attribute
{
    public string Name { get; }
    public AuthorAttribute(string name) => Name = name;
}

[Author("Brian Kernighan"), Author("Dennis Ritchie")]
class Class1 
{
    ...
}

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, urlet 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. ulongushortchardoubleintlongsbyteshortstringuint
  • 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 conteneur
  • module — 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 , , fieldparammethod, property, return, , , typeet 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éthode
    • return — 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érateur
    • return — 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ée
    • return — 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ée
    • param — 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énement
    • field — la cible est le champ
    • method — 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ée
    • param — 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.Attributela 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é de System.Attribute cette étape, le type est le résultat de cette étape.

Si exactement l’une des deux étapes ci-dessus entraîne un type dérivé System.Attributede , 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 et ExampleAttribute. L’attribut [Example] est ambigu, car il peut faire référence à l’un ou ExampleAttributel’autre Example . 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ée ExampleAttributeAttribute!). Si la déclaration de la classe Example est supprimée, les deux attributs font référence à la classe d’attribut nommée ExampleAttribute, 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 de Class1.

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 :
    • Valeur constante.
    • Objet System.Type obtenu à l’aide d’un typeof_expression (§12.8.18) spécifiant un type non générique, un type construit fermé (§8.4.3) ou un type générique non lié (§8.4.4), mais pas un type ouvert (§8.4.3).
    • Tableau unidimensionnel de attribute_argument_expressions.

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 Td’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 constructeur C d’instance sur T 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 dans N:
    • Supposons Name que l’identificateur de la named_argument Arg.
    • Name doit identifier un champ public ou une propriété en lecture-écriture non statique sur T. S’il T n’existe pas de champ ou de propriété de ce type, une erreur au moment de la compilation se produit.
  • Si l’une des valeurs dans positional_argument_listP ou l’une des valeurs de named_argument_listN est de type System.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 Td’attribut, le constructeur C d’instance surT, le positional_argument_listP, le named_argument_listN et l’entité Ede 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, CPet Nassocié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 constructeur C d’instance et les valeurs comme déterminé au moment de la compilation. Ces étapes entraînent une exception ou produisent une instance O de T.
  • Pour chaque named_argument Arg dans N, dans l’ordre :
    • Supposons Name que l’identificateur de la named_argument Arg. Si Name elle n’identifie pas un champ ou une propriété en lecture-écriture publique non statique sur O, une exception est levée.
    • Nous allons Value être le résultat de l’évaluation de la attribute_argument_expression de Arg.
    • Si Name elle identifie un champ sur O, définissez ce champ sur Value.
    • Sinon, Name identifie une propriété sur O. Définissez cette propriété sur Value.
    • Le résultat est O, une instance de la classe T d’attributs qui a été initialisée avec le positional_argument_list P et le named_argument_listN.

Remarque : Le format de stockage T, , CP( N et l’associant à E) dans A et le mécanisme permettant de spécifier E et de récupérer T, PCN à partir A (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) et System.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.Attributel’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 symboles ALPHA de compilation conditionnelle et BETA.

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 le virtual modificateur. Les remplacements de cette méthode sont implicitement conditionnels et ne doivent pas être explicitement marqués avec un Conditional 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. Class2la Test méthode appelle cette méthode. Étant donné que le symbole DEBUG de compilation conditionnelle est défini, s’il Class2.Test est appelé, il appelle M. Si le symbole DEBUG n’avait pas été défini, n’appelait Class2.Test Class1.Mpas .

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 et Class3 chacune contiennent des appels à la méthode Class1.Fconditionnelle, qui est conditionnelle selon qu’elle est définie ou non DEBUG . Étant donné que ce symbole est défini dans le contexte de Class2 mais pas Class3, l’appel à F dans Class2 est inclus, tandis que l’appel à F l’entrée Class3 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ément M 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 symbole DEBUG, 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 conditionnelle ALPHA et BETA.

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 et Class2 sont chacune décorées avec un attribut Test, qui est conditionnelle selon qu’elle est définie ou non DEBUG . Dans la mesure où ce symbole est défini dans le contexte de Class1 l’attribut Test activé, mais pas Class2dans le contexte, la spécification de l’attribut est Class1 incluse, tandis que la spécification de l’attribut Test est Class2 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’attribut Obsolete . Chaque utilisation de A résultats Main dans un avertissement qui inclut le message spécifié, « Cette classe est obsolète ; utiliser la classe B à 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 par string, T? devient une annotation nullable. Toutefois, ce code n’est pas légal, car T 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’argument value est garanti ne pas nullêtre . Toutefois, il est acceptable d’appeler ThrowWhenNull 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’attribut NotNullIfNotNull peut être utilisé. Considérez la méthode suivante :

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

Si l’argument url n’est pas null, 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 retourne true lorsque l’argument est null 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 retourne false. 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