I. Les attributs ? Ké za ko ?▲
I-A. C'est quoi ?▲
Un attribut est un petit morceau de code permettant de paramétrer un assembly, une classe, un constructeur, un délégué, une énumération, un événement, un champ, une interface, une méthode, un paramètre, une propriété, une valeur de retour, une structure ou un autre attribut, etc.
Ils ont une syntaxe particulière et ne peuvent pas être confondus avec d'autres instructions du langage.
[MyAttribut(true)]
public
class
A
{
}
I-B. À quoi ça sert ?▲
Les attributs servent donc à paramétrer une classe ou autre, afin que les appelants puissent facilement modifier leurs comportements en fonctions de ces paramètres.
Concrètement nous retrouvons les attributs dans beaucoup de classes de l'espace de nom System.Windows.Forms. Les attributs servent par exemple à définir l'image associée à un composant dans les boites à outils de nos EDI préférés.
Un autre attribut que nous rencontrons assez souvent est l'attribut Serializable. Dans ce cas l'attribut sert à « marquer » une classe comme étant sérialisable par le framework.
II. Utiliser les attributs▲
Maintenant que nous savons ce qu'est un attribut, voyons comment nous pouvons les exploiter dans nos programmes. L'utilisation des attributs est étroitement liée à la réflexion.
En effet c'est grâce à la réflexion que vous allez pouvoir déterminer si une classe, une propriété, un membre possèdent un ou plusieurs attributs.
MyClass myClass =
new
MyClass
(
);
Type cType =
myClass.
GetType
(
);
object
[]
atts =
cType.
GetCustomAttributes
(
false
);
//On obtient les attributs de la classe sans prendre en compte l'héritage
for
(
int
i =
0
;
i atts.
Length;
i++
)
{
if
(
atts[
i]
is
CustomAttribut )
{
Console.
WriteLine
(
((
CustomAttribut)atts[
i]
).
MyValue );
}
}
III. Comment créer ses propres attributs ?▲
Rien n'est plus simple que de créer ces propres attributs. ;-)
Pour cela il suffit de créer une classe dérivant de la classe Attribute de faire terminer le nom de sa classe par Attribute.
public
class
CustomAttribute :
Attribute
{
private
string
myValue;
public
CustomAttribute
(
string
myValue)
{
this
.
myValue =
myValue;
}
public
string
MyValue
{
get
{
return
this
.
myValue;
}
}
}
Cet attribut s'utilise ainsi :
[Custom(
"une valeur"
)]
public
class
UneClasse
{
}
La valeur définie dans l'attribut lors de son utilisation est obligatoire à cause du constructeur défini la classe CustomAttribute.
III-A. Définition de la portée d'un attribut▲
Les attributs peuvent être conçu de façon a ce qu'il ne soit valable que sur un certain type de structure de code.
Ainsi un attribut peut être dédié spécialement aux propriétés, aux classes, aux méthodes, etc.
Afin de rendre un attribut spécialisé pour une structure de code, il suffit de lui attribuer un … attribut.
[AttributeUsage(AttributeTargets.Property)]
public
class
CustomAttribut
{
private
string
myValue;
public
CustomAttribute
(
string
myValue)
{
this
.
myValue =
myValue;
}
public
string
MyValue
{
get
{
return
this
.
myValue;
}
}
}
L'énumération AttributeTargets peut prendre une multitude de valeurs. Je vous invite à voir la msdn afin de connaître les possibilités.
Liste des énumérations de l'énumération AttributeTargetsListe des enumérations de l'énumération AttributeTargets
Comment faire pour définir un attribut pouvant être utilisé à la fois sur une propriété et une méthode par exemple ? Il suffit de faire une addition binaire entre toutes les valeurs souhaitées.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
III-B. L'héritage des attributs▲
Une chose à laquelle on ne pense pas forcément c'est le problème de l'héritage. En effet que ce passe-t-il quand nous créons une classe qui hérite d'une autre qui définie par exemple des propriétés paramétrées avec des attributs ?
Eh bien là aussi le framework nous permet de personnaliser le comportement de l'héritage des attributs.
///
<
summary
>
/// Classe définissant l'attribut personnalisé
///
<
/summary
>
[AttributeUsage(AttributeTargets.Property, Inherited=true)]
public
class
DisplayAttribute :
Attribute
{
private
bool
display =
false
;
///
<
summary
>
/// Constructeur par défaut
///
<
/summary
>
public
DisplayAttribute
(
bool
display)
{
this
.
display =
display;
}
///
<
summary
>
/// Obtient ou défini une valeur indiquant si la propriété suivi a été modifié
///
<
/summary
>
public
bool
Display
{
get
{
return
this
.
display;
}
}
}
Le paramètre nommé Inherited permet de définir si l'attribut peut affecter les membres hérités.
Exemple :
public
class
Information
{
[Display(true)]
public
string
Name
{
get
{
return
this
.
name;
}
set
{
this
.
name =
value
;
}
}
}
public
class
InformationsSpecifiques :
Information
{
}
Dans cet exemple la classe InformationsSpecifiques hérite de la classe Information qui défini l'attribut Display à vrai pour la propriété Name. Que ce passe-t-il lorsqu'un objet de type InformationsSpécifique est examiné pour en extraire les attributs ? Dans notre cas la propriété Name définie par la classe Information va être examineé et l'attribut Display va être détecté, car nous avons défini le paramètre Inherited à true.
Au niveau du code de la détection y a-t-il un moyen d'obtenir les attributs dont la propriété Inherited à false ?
Contrairement à toute attente oui … En effet le fait de paramétrer un attribut avec Inherited=false, n'empêche pas les « inspecteurs » d'obtenir la valeur de l'attribut avec le code suivant :
pi.
GetCustomAttributes
(
typeof
(
DisplayAttribute),
true
);
En effet le fait de positionner le second argument de GetCustumAttributes (inherited) à « vrai » permet d'obtenir les attributs hérités.
III-C. Instance multiple des attributs▲
Une autre question qui se pose est : « Est-il possible d'avoir plusieurs fois le même attribut sur le même élément ? »
Là encore la réponse nous est donnée par un paramètre nommé : AllowMultiple. Si ce paramètre est défini à true vous pourrez mettre deux fois l'attribut, sinon non.
public
class
Information
{
[Author(
"dev01"
)]
[Author(
"author"
)]
public
string
Name
{
get
{
return
this
.
name;
}
set
{
this
.
name =
value
;
}
}
}
IV. Cas concret d'utilisation des attributs personnalisés▲
Tout ceci est un peu trop théorique. C'est pourquoi nous allons voir un exemple d'utilisation des attributs personnalisés à travers un système permettant de paramétrer l'affichage des propriétés.
IV-A. La classe à paramétrer▲
La classe ci-dessous sera la classe dont nous allons paramétrer l'affichage.
public
class
Informations
{
private
string
name;
private
string
firstName;
private
string
mail;
public
Informations
(
)
{
}
public
string
Name
{
get
{
return
this
.
name;
}
set
{
this
.
name =
value
;
}
}
}
IV-B. La classe d'attribut personnalisé▲
Cette classe définit notre attribut qui nous permettra de savoir si les informations de la classe doivent être visibles ou non.
[AttributeUsage(AttributeTargets.Property, Inherited=true, AllowMultiple = false)]
public
class
DisplayAttribute :
Attribute
{
private
bool
display;
public
DisplayAttribute
(
bool
display)
{
this
.
display =
display;
}
public
bool
Display
{
get
{
return
this
.
display;
}
set
{
this
.
display =
value
;
}
}
}
L'attribut que nous avons défini prend un paramètre dans le constructeur afin de définir si la propriété doit être affichée ou non.
La propriété qui nous intéresse ici est Display.
IV-C. Utilisation de notre attribut personnalisé▲
Voyons maintenant comment nous allons nous servir de notre attribut.
public
class
Informations
{
private
string
name;
private
string
firstName;
private
string
mail;
public
Informations
(
)
{
}
[Display(true)]
public
string
Name
{
get
{
return
this
.
name;
}
set
{
this
.
this
.
name =
value
;
}
}
[Display(true)]
public
string
FirstName
{
get
{
return
this
.
firstName;
}
set
{
this
.
firstName =
value
;
}
}
public
string
Mail
{
get
{
return
this
.
mail;
}
set
{
this
.
mail =
value
;
}
}
}
L'utilisation de notre attribut dans la classe à paramétrer est simple.
Il nous suffit de le définir à true pour dire que nous voulons que la propriété soit affichée.
IV-D. La classe de gestion de l'affichage des propriétés▲
Cette classe permet d'afficher ou non une propriété en fonction de l'attribut Display.
private
void
UpdateInformations
(
)
{
this
.
listViewInformations.
Clear
(
);
this
.
listViewInformations.
Columns.
Clear
(
);
if
(
this
.
informations !=
null
)
{
PropertyInfo[]
pis =
this
.
informations.
GetType
(
).
GetProperties
(
);
foreach
(
PropertyInfo pi in
pis)
{
foreach
(
object
o in
pi.
GetCustomAttributes
(
typeof
(
DisplayAttribute),
false
))
{
if
(((
DisplayAttribute)o).
Display)
{
this
.
listViewInformations.
Columns.
Add
(
pi.
Name);
}
}
}
List<
string
>
val =
new
List<
string
>(
);
for
(
int
i =
0
;
i <
this
.
listViewInformations.
Columns.
Count;
i++
)
{
val.
Add
(
this
.
informations.
GetType
(
).
GetProperty
(
this
.
listViewInformations.
Columns[
i].
Text).
GetGetMethod
(
).
Invoke
(
this
.
informations,
null
).
ToString
(
)
);
}
this
.
listViewInformations.
Items.
Add
(
new
ListViewItem
(
val.
ToArray
(
)));
}
}
Comme vous le voyez, l'utilisation des attributs s'appuie beaucoup sur la réflexion.
Dans le détail cette fonction obtient la totalité des propriétés de la classe, et extrait pour chaque propriété la liste des attributs de type DisplayAttribute.
Ensuite si la propriété doit être affichée alors le nom de la propriété est ajouté comme colonne dans le ListView.
La suite est assez simple, il s'agit de remplir la ligne avec les informations.
V. Conclusion▲
L'utilisation des attributs personnalisés est très utile afin de paramétrer une classe au moment du développement pour influencer le comportement de composant utilisant cette classe.
C'est un moyen simple et efficace de fournir des informations sur la façon d'utiliser une classe.