Programmation C sharp/Delegates et events

Programmation C#
Programmation C#
Modifier ce modèle

Les délégués

modifier

Un 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

modifier

La 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

modifier

Ce 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

modifier

Une 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

modifier

Un 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

modifier

Il 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

modifier

La 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

modifier

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

modifier

Les 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

modifier

La 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 accesseurs add et remove 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

modifier

Les é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.