CodeGym /Cours /C# SELF /Analyse détaillée de l'abonnement ( +=)

Analyse détaillée de l'abonnement ( +=)

C# SELF
Niveau 53, Leçon 0
Disponible

1. Introduction

Les événements en C# — ce ne sont pas juste des variables dans lesquelles on peut mettre un delegate. C'est une liste protégée de handlers, et seul le propriétaire de l'événement peut déclencher son exécution ; les autres peuvent seulement ajouter (+=) ou retirer (-=) leurs réactions.

Par exemple, on peut s'abonner à un événement comme ceci :

worker.WorkCompleted += Worker_WorkCompleted;

À première vue, cela ressemble à une addition ordinaire, mais en réalité ça fonctionne différemment. Sous le capot, l'événement garde une chaîne d'invocation (invocation list) — un ensemble de delegates qu'il faut appeler quand l'événement se produit. Quand vous écrivez +=, un nouveau handler est ajouté à cette liste.

Voyons en détail ce qui se passe à l'intérieur, comment cette chaîne se forme et quelles nuances peuvent apparaître lors de l'abonnement.

Qu'est-ce qu'une chaîne de delegates ?

Rappel : les delegates en C# sont "multicast" : on peut leur assigner plusieurs méthodes, et elles s'exécuteront toutes dans l'ordre si le delegate est invoqué. Les événements utilisent ce mécanisme : leur valeur est, en substance, un delegate avec une liste de handlers.

En termes de code :

public event EventHandler<WorkCompletedEventArgs> WorkCompleted;

Quand quelqu'un s'abonne :

worker.WorkCompleted += MyHandler;

C# fait à peu près ceci sous le capot :

  • Prend le delegate courant (la liste de handlers).
  • Appelle pour lui la méthode Delegate.Combine (combine les handlers).
  • Réécrit la chaîne mise à jour dans la variable de l'événement.

Schématiquement :

Opération Liste interne des handlers de l'événement
Avant null ou [Handler1]
Après += Handler2 [Handler1, Handler2]
Après encore += H3 [Handler1, Handler2, Handler3]

Fait intéressant : le mécanisme des multicast delegates — ce n'est pas de la "magie", mais une implémentation concrète : le delegate contient en interne un tableau de méthodes à appeler.

2. Comment fonctionne l'abonnement : explication simple

Prenons un exemple concret. Supposons qu'on ait un publisher (Worker) et des subscribers (Logger, Notifier) :

public class Worker
{
    public event EventHandler<WorkCompletedEventArgs> WorkCompleted;

    public void DoWork()
    {
        // ... travail ...
        OnWorkCompleted("Tâche terminée !");
    }

    protected virtual void OnWorkCompleted(string message)
    {
        WorkCompleted?.Invoke(this, new WorkCompletedEventArgs { Message = message });
    }
}

public class Logger
{
    public void LogWorkCompleted(object? sender, WorkCompletedEventArgs e)
    {
        Console.WriteLine("Log : " + e.Message);
    }
}

public class Notifier
{
    public void ShowNotification(object? sender, WorkCompletedEventArgs e)
    {
        Console.WriteLine("Notification : " + e.Message);
    }
}

Dans Main :

var worker = new Worker();
var logger = new Logger();
var notifier = new Notifier();

worker.WorkCompleted += logger.LogWorkCompleted;
worker.WorkCompleted += notifier.ShowNotification;

// Lancement du travail
worker.DoWork();

Quand OnWorkCompleted est appelé, l'événement invoque d'abord logger.LogWorkCompleted, puis notifier.ShowNotification (dans l'ordre où vous vous êtes abonnés).

3. Nuances utiles

Visualisation : comment l'événement stocke les handlers

+---------------------+
| Worker              |
|---------------------|
| WorkCompleted Event |    ---> [ LogWorkCompleted, ShowNotification ]
+---------------------+

Lors de l'abonnement, chaque nouveau handler "s'accroche" à la liste existante. Après l'appel de l'événement, le delegate appelle tour à tour toutes les méthodes abonnées.

Abonnement multiple du même method

worker.WorkCompleted += logger.LogWorkCompleted;
worker.WorkCompleted += logger.LogWorkCompleted; // Deux fois !

Dans ce cas, le handler sera appelé autant de fois qu'il est abonné — ici donc deux fois de suite.

Abonnement avec une lambda

worker.WorkCompleted += (sender, e) => Console.WriteLine("Handler anonyme : " + e.Message);

Si vous abonnez la même lambda plusieurs fois — pareil, elle sera appelée plusieurs fois à chaque événement. Attention cependant : chaque lambda produit son propre objet delegate, on ne peut pas se désabonner aussi simplement (voir la leçon 260 et suivantes pour plus de détails).

Comment fonctionne l'abonnement en interne : analyse bas niveau

L'événement est une propriété spéciale avec deux accesseurs (add/remove) qui sont appelés quand on utilise += et -=. En simplifiant, le compilateur génère à peu près ce code :

// À peu près comme ça (simplifié)
public event EventHandler<WorkCompletedEventArgs> WorkCompleted
{
    add { /* code d'ajout du handler */ }
    remove { /* code de suppression du handler */ }
}

Par défaut, l'implémentation standard combine les delegates avec Delegate.Combine et les retire via Delegate.Remove.

Ça protège l'événement : de l'extérieur on ne peut pas déclencher l'événement directement (pas de worker.WorkCompleted(...);), on peut seulement s'abonner ou se désabonner.

Mécanique d'abonnement : schéma

             +----------------------+
             |                      |
             v                      v
    +--------------------+   +----------------------+
    | LogWorkCompleted   |   | ShowNotification    |
    +--------------------+   +----------------------+
             ^                      ^
             \______________________/
                       ^
                       |
               WorkCompleted Event

La fameuse "Invocation List" — la chaîne d'appels.

Pourquoi c'est important : valeur pratique

Comprendre la mécanique d'abonnement est la clé pour gérer les liens entre objets. Tu peux construire des systèmes complexes où les composants s'abonnent et se désabonnent dynamiquement aux événements, sans créer de dépendances fortes. C'est standard dans les UI frameworks, les game engines, les applications serveur et même dans les architectures modernes de microservices (là-bas c'est plutôt au niveau des queues, mais l'idée est similaire).

En entretien, on te demandera souvent comment marche le modèle d'événements en C#, pourquoi les events rendent le système flexible, et comment bien gérer le cycle de vie des abonnements.

4. Ce qu'on peut (et ne peut pas) faire depuis l'extérieur de la classe

On peut

  • S'abonner à l'événement (+=)
  • Se désabonner (-=)

On ne peut pas

  • Appeler l'événement directement
  • Assigner directement un delegate à l'événement (worker.WorkCompleted = ... — erreur !)

Ces restrictions sont réalisées grâce au mot-clé event. Si tu déclares le delegate comme un champ ordinaire :

public EventHandler<WorkCompletedEventArgs> WorkCompleted; // pas event !

— n'importe qui pourrait tout faire, y compris réinitialiser les handlers, ce qui créerait du bazar et des bugs potentiels. C'est pour ça qu'il faut presque toujours utiliser seulement event !

Peut-on abonner la même méthode à plusieurs événements ?

Oui ! C'est appelé "multi-subscribing". Par exemple :

worker.WorkCompleted += logger.LogWorkCompleted;
anotherWorker.WorkCompleted += logger.LogWorkCompleted;

Si tu as abonné la même méthode aux événements de différents objets, le handler réagira à chacun d'eux. Dans le handler tu peux déterminer qui a déclenché l'événement via le paramètre sender.

Cas réel : abonnements dynamiques

Imaginons une application où l'utilisateur peut lancer plusieurs tâches en parallèle. Pour chaque nouvelle tâche on crée un objet Worker, et on s'abonne au même handler — par exemple la méthode du logger logger.LogWorkCompleted.

var allWorkers = new List<Worker>();
for (int i = 0; i < 10; i++)
{
    var w = new Worker();
    w.WorkCompleted += logger.LogWorkCompleted;
    allWorkers.Add(w);
}

En conséquence, quand l'une des tâches se termine, le logger recevra une notification et enregistrera ce qui s'est passé et quand.

Aspects pratiques : comment voir les subscribers d'un événement

Dans du code normal, tu ne peux pas savoir directement combien de handlers sont abonnés à un événement (l'événement encapsule le delegate). Cependant, à l'intérieur de la classe publisher tu peux travailler directement avec le delegate de l'événement, par exemple inspecter son GetInvocationList() :

// Seulement à l'intérieur de la classe publisher
var handlers = WorkCompleted?.GetInvocationList();
if (handlers != null)
    Console.WriteLine($"Abonnés : {handlers.Length}");

Cela sert si tu dois faire une logique d'envoi personnalisée ou du debugging (mais mieux vaut l'utiliser uniquement à des fins pédagogiques !).

5. Erreurs typiques et particularités

Handler non ajouté ? Il ne sera pas appelé !
Si tu ne t'es pas abonné, le handler ne sera jamais appelé. Avant d'invoquer l'événement, vérifie toujours la null-ité (sinon NullReferenceException).

Abonnement répété
Si tu t'es abonné plusieurs fois au même événement avec la même méthode — le handler s'exécutera autant de fois. Parfois c'est un piège : un += accidentel transforme une notification en plusieurs identiques.

Méthodes static/instance
Tu peux t'abonner à la fois à des méthodes static et d'instance. L'essentiel est d'avoir la bonne signature.

public static void StaticHandler(object? sender, WorkCompletedEventArgs e) { /* ... */ }
worker.WorkCompleted += StaticHandler;

Lambdas et abonnements dans une boucle
Si, par exemple, dans une boucle tu t'abonnes avec des lambdas, assure-toi de comprendre ce qu'il arrive aux variables capturées par la lambda. Tu peux accidentellement capturer une valeur différente de celle que tu attendais.

2
Mission
C# SELF, niveau 53, leçon 0
Bloqué
Abonnement dynamique et affichage des abonnés
Abonnement dynamique et affichage des abonnés
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION