Programmation C sharp/Delegates et events
Les délégués
modifierUn délégué (delegate en anglais) est l'équivalent .Net d'un pointeur de fonction. Son rôle est d'appeler une ou plusieurs méthodes qui peuvent varier selon le contexte.
Syntaxe
modifierLa déclaration d'un délégué définit la signature d'une méthode, dont le type de retour est précédé du mot clé delegate
.
Exemple :
public delegate int CompareDelegate(object a,object b);
Cette déclaration produit en fait une classe CompareDelegate
dérivant de la classe System.Delegate
. Il est donc possible de la placer en dehors de toute classe ou espace de noms, comme pour une classe normale.
Utilisation
modifierCe délégué définit donc un nouveau type qui peut être utilisé en paramètre d'une méthode. L'appel à un délégué utilise la même syntaxe que celle d'une méthode.
Exemple :
public void TrierTableau(object[] objects, CompareDelegate compare)
{
int moves;
// Vérifier que le délégué pointe sur une méthode :
if (compare==null) return;
do
{
moves = 0;
for (int i=1 ; i<objects.Length ; i++)
// Si objects[i-1] > objects[i]
// appel au délégué pour comparer deux objets
if ( compare( objects[i-1], objects[i]) > 0 )
{
// Échange des deux objets mal ordonnés
object o=objects[i-1];
objects[i-1]=objects[i];
objects[i]=o;
moves++; // un échange de plus
}
}
while (moves!=0);
}
Instanciation
modifierUne variable du type délégué peut être déclarée, comme avec une classe normale :
CompareDelegate comparateur;
Initialement, cette variable vaut null
car ne référence aucune méthode.
Ajout et retrait de fonction
modifierUn délégué est associé à une ou plusieurs méthodes qui possèdent toutes la même signature que celui-ci.
L'ajout ou le retrait de fonction se fait par les opérateurs =
, +=
et -=
.
Exemple : soit les deux méthodes suivantes :
void AfficherConsole(string message)
{ ... }
void AfficherFenetre(string message)
{ ... }
et le délégué suivant :
delegate void AfficherDelegate(string message);
AfficherDelegate affichage;
affichage = AfficherConsole;
affichage += AfficherFenetre;
affichage("Un message affiché de deux manières différentes en un seul appel");
affichage -= AfficherFenetre; // Ne plus afficher par fenêtre
affichage("Un message sans fenêtre");
affichage = AfficherFenetre; // Fenêtre seulement (affectation par =)
affichage("Un message dans une fenêtre");
Il est également possible d'utiliser la syntaxe suivante équivalente à la précédente :
affichage += new AfficherDelegate( AfficherFenetre );
affichage -= new AfficherDelegate( AfficherFenetre );
Délégué anonyme
modifierIl est possible de créer dynamiquement la fonction associée à un délégué en utilisant la syntaxe anonyme suivante :
delegate( arguments ) { code }
Exemple :
affichage += delegate(string m)
{ Console.WriteLine("Nouveau message : "+m); }
La spécification des arguments est optionnelle, à condition qu'aucun des arguments ne soit utilisé par la fonction. Ce qui ne peut être le cas si un des arguments est de type out
, car la fonction doit obligatoirement lui attribuer une valeur.
Exemple :
affichage += delegate
{ Console.WriteLine("Fonction qui n'utilise pas les arguments"); }
Les délégués du framework .Net
modifierLa plupart des délégués du framework .Net sont utilisés comme callback, c'est à dire appelés quand un événement se produit (timer, opération terminée, exécution d'un thread, ...). La signature de ces délégués comporte en général un paramètre nommé state
de type object
. Ce paramètre correspond à la valeur transmise au paramètre state
de la méthode appelée utilisant le délégué.
Ce paramètre étant de type object
permet de transmettre toute sorte de valeurs : objets, valeurs numériques (grâce à l'auto-boxing), tableaux de valeurs, ...
Les événements
modifierUn événement (event en anglais) est déclenché en dehors de l'application, par l'utilisateur (frappe au clavier, clic d'un bouton de souris, ...), par le système (connexion réseau, ...), par une autre application.
Gestion par délégué
modifierLes délégués sont utilisés pour gérer les événements. Toutefois, cela pose un problème si on utilise un délégué public dans une classe :
public delegate void PageRecueDelegate(string url, string contenu);
public class ConnectionHttp
{
public PageRecueDelegate PageRecue;
}
Le code utilisant cette classe peut être le suivant :
ConnectionHttp getweb = new ConnectionHttp();
getweb.PageRecue = StockerPageRecue;
getweb.PageRecue += AfficherPageRecue;
getweb.PageRecue("",""); // appel bidon
Les trois principaux problèmes sont les suivants :
- Si l'objet est partagé par plusieurs classes, fonctions, threads ou avec le système (comme c'est le cas pour les composants de l'interface graphique), plusieurs fonctions pourraient déjà avoir été associées au délégué
PageRecue
, et pourraient être supprimées du délégué par une simple affectation, - L'ajout et le retrait de fonctions au délégué n'est pas thread-safe,
- L'appel au delegate ne devrait pas être possible en dehors de la classe
ConnectionHttp
.
Solution : event
modifier
Les trois problèmes cités précédemment sont résolus par le mot clé event
:
public delegate void PageRecueDelegate(string url, string contenu);
public class ConnectionHttp
{
public event PageRecueDelegate PageRecue;
}
Ce mot clé protège l'accès au délégué de la manière suivante :
- Il n'est plus possible d'utiliser l'affection seule (opérateur
=
), il faut utiliser+=
ou-=
; - L'ajout et le retrait sont réalisés de manière synchrone,
- Il n'est pas possible d'appeler le
delegate
en dehors de la classe où l'event
est déclaré.
Fonctionnement interne
modifierLa protection est réalisée de la manière suivante :
- Le véritable membre délégué est privé (même si l'
event
est public) ; - L'utilisation des opérateurs
+=
et-=
est réalisée par des appels aux accesseursadd
etremove
de l'event
.
Il est possible de remplacer les accesseurs par défaut créés par le compilateur. Pour l'exemple précédent, les accesseurs par défaut sont définis ainsi :
public delegate void PageRecueDelegate(string url, string contenu);
public class ConnectionHttp
{
private PageRecueDelegate pageRecue;
public event PageRecueDelegate PageRecue
{
[MethodImpl(MethodImplOptions.Synchronized)]
add // paramètre value : fonction à ajouter
{
pageRecue += value;
}
[MethodImpl(MethodImplOptions.Synchronized)]
remove // paramètre value : fonction à retirer
{
pageRecue -= value;
}
}
}
Les événements du framework .Net
modifierLes événements sont principalement utilisés dans le framework .Net pour les interfaces graphiques. Le délégué correspondant ne retourne rien (void
) et possède deux paramètres : un objet indiquant la source de l'événement (le contrôle), et un objet du type nom_événementEventArgs
dérivant de la classe EventArgs
, contenant d'autres informations sur l'événement.