Programmation C sharp/Attributs

Programmation C#
Modifier ce modèle

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 attributModifier

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'utilisationModifier

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 :

...
    [STAThread]
    public static void Main()
    {
        ...
    }
...

L'attribut associé à la méthode Main() est nommé STAThread et ne comporte aucun paramètre.

Nom raccourci ou nom longModifier

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 :

    [STAThreadAttribute]
    public static void Main()
    {
        ...
    }

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ètresModifier

Deux exemples d'attribut comportant des paramètres :

Exemple 1:

    [Obsolete("Veuillez ne plus utiliser cette méthode.")]
    public void AncienneMethode()
    {
        ...
    }

Exemple 2:

    [DllImport("User32.dll", EntryPoint="MessageBox")]
    static extern int MessageDialog(int hWnd, string msg, string caption, int msgType);

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éclarationModifier

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 :

    [Obsolete("Veuillez ne plus utiliser cette méthode."),
     DllImport("User32.dll", EntryPoint="MessageBox")]
    static extern int MessageDialog(int hWnd, string msg, string caption, int msgType);

Ou bien de les mettre dans des blocs différents :

    [Obsolete("Veuillez ne plus utiliser cette méthode.")]
    [DllImport("User32.dll", EntryPoint="MessageBox")]
    static extern int MessageDialog(int hWnd, string msg, string caption, int msgType);

Cible d'un attributModifier

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 :

[assembly: AssemblyTitle("Essai")]

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 attributModifier

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 classeModifier

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 :

using System;
public class ExempleAttribute : Attribute
{
}

Cet attribut peut déjà être utilisé tel qu'il est, sans paramètres :

[Exemple]
public class UneClasse
{
    [method:Exemple]
    [return:Exemple]
    public int UneMethode(int UnParametre)
    {
        return UnParametre;
    }
}

Définir les paramètres positionnelsModifier

Les paramètres positionnels (ou ordonnés) correspondent aux paramètres du constructeur. Exemple :

using System;
public class ExempleAttribute : Attribute
{
     private string titre;
     public ExempleAttribute(string titre)
     { this.titre = titre; }
}

[Exemple("Un exemple de classe")]
public class UneClasse
{
    [method:Exemple("Une méthode")]
    [return:Exemple("Retourne le paramètre passé")]
    public int UneMethode(int UnParametre)
    {
        return UnParametre;
    }
}

Un attribut peut avoir plusieurs constructeurs différents pour définir différents types de paramètres positionnels. Exemple :

using System;
public class ExempleAttribute : Attribute
{
     private string titre,commentaire;
     public ExempleAttribute(string titre)
     {
         this.titre = titre;
         this.commentaire = "Sans commentaire";
     }
     public ExempleAttribute(string titre,string commentaire)
     {
         this.titre = titre;
         this.commentaire = commentaire;
     }
}

[Exemple("Un exemple de classe",
         "Cette classe est un exemple")]
public class UneClasse
{
    [method:Exemple("Une méthode")]
    [return:Exemple("Retourne le paramètre passé")]
    public int UneMethode(int UnParametre)
    {
        return UnParametre;
    }
}

Définir les paramètres nommésModifier

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

using System;
public class ExempleAttribute : Attribute
{
     private string titre;
     public ExempleAttribute(string titre)
     {
         this.titre = titre;
         numero = 0; // valeur par défaut
     }
     public int numero;
}

[Exemple("Un exemple de classe", numero=1)]
public class UneClasse
{
    [method:Exemple("Une méthode")]
    [return:Exemple("Retourne le paramètre passé", numero=2)]
    public int UneMethode(int UnParametre)
    {
        return UnParametre;
    }
}

Définir la cibleModifier

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 :

using System;
[ AttributeUsage( AttributeTargets.Class |   // cible classe
                  AttributeTargets.Struct,   // ou structure
                  AllowMultiple = false ) ]  // une seule fois par classe ou structure
public class ExempleAttribute : Attribute
{
     private string titre;
     public ExempleAttribute(string titre)
     {
         this.titre = titre;
         numero = 0; // valeur par défaut
     }
     public int numero;
}

[Exemple("Un exemple de classe", numero=1)]
public class UneClasse
{
    // erreur de compilation pour les 2 attributs suivants
    // car l'attribut ne peut concerner une méthode ou une valeur de retour
    [method:Exemple("Une méthode")]
    [return:Exemple("Retourne le paramètre passé", numero=2)]
    public int UneMethode(int UnParametre)
    {
        return UnParametre;
    }
}

// erreur de compilation car l'attribut est utilisé plus d'une fois
[Exemple("Un autre exemple de classe", numero=1)]
[Exemple("Un exemple de classe", numero=2)]
public class UneAutreClasse
{
}

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 attributsModifier

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 :

Type maclasse = typeof(UneClasse);

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 :

Type maclasse = typeof(UneClasse);
System.Attribute[] attributs = System.Attribute.GetCustomAttributes(maclasse);

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 :

foreach(Attribute attr in attributs)
    if (attr is ExempleAttribute)
    {
        ExempleAttribute ex=(ExempleAttribute)attr;
        Console.WriteLine("Exemple : " + ex.titre );
    }

Attributs prédéfinisModifier

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

Attribut ConditionalModifier

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 :

[Conditional("DEBUG")]
public void trace(string message) { ... }

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) :

[Conditional("DEBUG"),Conditional("FORCEDEBUG")]
public void trace(string message) { ... }

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 :

[Conditional("DEBUG")]
public class Documentation : System.Attribute
{
    string text;

    public Documentation(string text)
    {
        this.text = text;
    }
}

class ExempleDeClasse
{
    // Cet attribut ne sera inclus que si DEBUG est défini.
    [Documentation("Cette méthode affiche un entier.")]
    static void DoWork(int i)
    {
        System.Console.WriteLine(i.ToString());
    }
}

Attribut ObsoleteModifier

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.

[Obsolete("Utilisez plutôt UneNouvelleMethode()")]
public void UneAncienneMethode()
{ ... }

Lors de l'appel à la méthode :

UneAncienneMethode();

le compilateur génèrera un avertissement comportant le message spécifié :

Utilisez plutôt UneNouvelleMethode()

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 :

[Obsolete("Utilisez plutôt UneNouvelleMethode()",true)]
public void UneAncienneMethode()
{ ... }

Lors de l'appel à cette méthode, le compilateur génèrera une erreur comportant le message spécifié :

Utilisez plutôt UneNouvelleMethode()

Attribut AttributeUsageModifier

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 DllImportModifier

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 FlagsModifier

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 ThreadStaticModifier

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.