# Programmation C sharp/Attributs

En C#, chaque déclaration de classe, méthode, propriété, ou variable peut être précédée d'un ou plusieurs attributs.

## Rôle d'un attribut

Le rôle d'un attribut est d'associer des informations, des méta-données à une déclaration. Ces données peuvent être utilisées par le compilateur, ou le Framework .NET, ou par l'application elle-même.

## Syntaxe d'utilisation

Un attribut comporte un nom et éventuellement des paramètres. Il est encadré par des crochets. Par exemple, Visual Studio .NET génère la fonction Main de la manière suivante :
L'attribut associé à la méthode Main est nommé STAThread et ne comporte aucun paramètre.

### Nom raccourci ou nom long

Dans l'exemple précédent, l'attribut STAThread est en fait géré par une classe nommée STAThreadAttribute qui dérive de la classe System.Attribute.
L'exemple fonctionne également avec le code source suivant :
Par convention, toute classe définissant un attribut doit porter un nom se terminant par Attribute. Ce suffixe est optionnel, le compilateur recherchera d'abord le nom indiqué, puis s'il ne trouve pas la classe, il ajoute le suffixe pour effectuer une seconde recherche.

### Attribut avec paramètres

Deux exemples d'attribut comportant des paramètres :
Exemple 1:
Exemple 2:
Un attribut possède deux catégories de paramètres :
* Les paramètres ordonnés : ils ne sont pas nommés, et doivent être placés dans le bon ordre, et avant les paramètres nommés ;
* Les paramètres nommés : le nom du paramètre est suivi du signe égal et de la valeur affectée au paramètre.

### Plusieurs attributs par déclaration

Plusieurs attributs peuvent être associés à une déclaration. Il est possible de les mettre dans le même bloc de crochets, en les séparant par une virgule :
Ou bien de les mettre dans des blocs différents :

### Cible d'un attribut

Un attribut est associé à la déclaration qui suit (sa cible), mais il existe des cas où la cible est ambiguë. En effet, il existe des attributs globaux associés à l'assembly lui-même et ne concerne pas une déclaration particulière. De même, les attributs concernant la valeur de retour d'une méthode sont placés au même endroit que ceux concernant la méthode elle-même.
Pour lever l'ambiguïté, il est possible de préciser la cible des attributs contenus dans le bloc de crochets. Le nom de la cible est suivi du signe deux-points ( : ).
Exemple :
Les cibles possibles sont :
* assembly : attributs concernant l'assembly (ne précède aucune déclaration particulière),
* module : attributs concernant le module (ne précède aucune déclaration particulière),
* type : attributs concernant la classe, la structure, l'interface, l'énumération ou le delegué,
* method : attributs concernant la méthode, l'accesseur get ou set d'une propriété, l'accesseur add ou remove d'un évènement, ou l'évènement,
* return : attributs concernant la valeur retournée par la méthode, le délégue, l'accesseur get ou set d'une propriété,
* param : attributs concernant un paramètre (méthode, délégue, accesseur set d'une propriété, accesseur add ou remove d'un évènement),
* field : attributs concernant le champ, ou l'évènement,
* property : attributs concernant la propriété ou l'indexeur,
* event : attributs concernant l'évènement.

## Créer un nouvel attribut

Le langage C# permet de créer de nouveaux attributs pour, par exemple, documenter une partie du code ou associer des données particulières à une déclaration.

### Créer la classe

Pour créer un nouvel attribut, il faut créer une classe dérivant de la classe System.Attribute, et la nommer avec le suffixe Attribute. Exemple :
Cet attribut peut déjà être utilisé tel qu'il est, sans paramètres :

### Définir les paramètres positionnels

Les paramètres positionnels (ou ordonnés) correspondent aux paramètres du constructeur. Exemple :
Un attribut peut avoir plusieurs constructeurs différents pour définir différents types de paramètres positionnels. Exemple :

### Définir les paramètres nommés

Les paramètres nommés correspondent à des champs ou propriétés publics, ils sont optionnels :

### Définir la cible

Par défaut l'attribut concerne tous les types de cibles (All).
Pour définir les cibles que l'attribut peut concerner, il faut utiliser l'attribut System.AttributeUsage sur la classe de l'attribut. Exemple :
L'énumération AttributeTargets possède les valeurs suivantes :
* All : toutes les cibles possibles. Cette valeur est la combinaison par ou logique de toutes les autres valeurs,
* Assembly,
* Module,
* Class : déclaration d'une classe,
* Struct : déclaration d'une structure,
* Interface : déclaration d'une interface,
* Constructor : constructeur d'une classe,
* Delegate,
* Event,
* Enum,
* Field,
* Method : déclaration d'une méthode,
* Property : déclaration d'une propriété,
* Parameter,
* ReturnValue,
* GenericParameter : paramètre d'un générique (template).

## Accès dynamique aux attributs

L'accès aux attributs personnalisés se fait en utilisant la réflexion qui permet d'accéder dynamiquement aux différents éléments des déclarations (attributs, méthodes d'une classe, ...).
La classe Type représente un type de données : une classe, une structure. L'opérateur typeof retourne le Type de son argument.
Exemple :
La classe System.Attribute possède une méthode statique nommée GetCustomAttributes prenant un Type en paramètre (ou tout autre objet de réflexion tel que méthode, propriété, ...) et retourne un tableau d'attributs :
L'opérateur is permet de tester le type réel de l'attribut, et par exemple retrouver l'attribut Exemple définit dans la section précédente :

## Attributs prédéfinis

Le langage C# définit un nombre d'attributs ayant un rôle spécifique lors de la compilation.

### Attribut Conditional

L'attribut System.Diagnostics.ConditionalAttribute s'applique à une méthode qui ne doit être appelée et définie que si le symbole spécifié est défini. Il peut s'agir par exemple d'une méthode de déboggage. Le symbole DEBUG est souvent utilisé pour distinguer les versions Debug et Release des projets sous Visual Studio.
Exemple :
Si l'attribut est utilisé plus d'une fois, la méthode n'est appelée et définie que si l'un des symboles est défini (Ou logique) :
Cet attribut évite d'encadrer systématiquement chaque appel de la méthode par des directives #if...#endif.
Il est également applicable aux classes d'attributs. Dans ce cas, les informations associées à l'attribut ne sont ajoutées que si le symbole est défini.
Par exemple :

### Attribut Obsolete

L'attribut System.ObsoleteAttribute est utilisé pour marquer une entité dont l'utilisation n'est plus recommandée. Le compilateur peut alors générer une erreur ou un avertissement selon les paramètres de l'attribut.
Lors de l'appel à la méthode :
le compilateur génèrera un avertissement comportant le message spécifié :
Si une valeur booléenne est spécifée à la suite du message, elle indique si une erreur doit être générée au lieu d'un avertissement.
Exemple :
Lors de l'appel à cette méthode, le compilateur génèrera une erreur comportant le message spécifié :

### Attribut AttributeUsage

L'attribut System.AttributeUsageAttribute déjà vu précédemment, indique l'utilisation d'une classe attribut.
Le seul paramètre obligatoire est une valeur ou une combinaison de valeurs de l'énumération System.AttributeTargets indiquant les cibles acceptables pour l'attribut.
Le paramètre AllowMultiple (bool, false par défaut) indique si l'attribut peut être utilisé plusieurs fois pour la même cible.
Le paramètre Inherited (bool, true par défaut) indique si l'attribut est hérité par les sous-classes.

### Attribut DllImport

L'attribut System.Runtime.InteropServices.DllImportAttribute permet d'importer une fonction définie dans une DLL externe.
Le nom de la DLL où la fonction est définie est le seul paramètre obligatoire.

### Attribut Flags

L'attribut System.FlagsAttribute s'applique aux énumérations pour indiquer que plusieurs valeurs peuvent être combinées avec l'opérateur ou ( | ). Cet attribut indique au compilateur de gérer les combinaisons de constantes dans la méthode ToString de cette énumération.

### Attribut ThreadStatic

L'attribut System.ThreadStaticAttribute s'applique aux variables membres statiques. Cet attribut indique que la variable est allouée pour tout nouveau thread. Ce qui signifie que chaque thread possède une instance différente de la variable. L'initialisation d'une telle variable à la déclaration n'est effectuée que pour l'instance du thread ayant chargé la classe.