1. Introduction
En travaillant avec des programmes, vous rencontrez inévitablement des situations où une partie doit «prévenir» les autres qu'il s'est passé quelque chose d'important. L'exemple classique — l'utilisateur a cliqué avec la souris, et cet événement doit être traité. Dans la vie quotidienne nous vivons déjà dans un monde d'événements : la bouilloire siffle dans la cuisine — vous entendez le signal et vous dépêchez d'éteindre la plaque. Le café s'est renversé sur le clavier — votre cœur a raté un battement — et vous vous empressez de sauver le laptop. La programmation suit les mêmes règles.
Événement — c'est un mécanisme qui permet à un objet-source (l'éditeur) d'avertir d'autres objets (les abonnés) des changements ou actions qui se sont produits. C'est une sorte de «j'ai signalé — qui a écouté, a répondu».
En C# les événements sont une construction spéciale basée sur un type de delegate. Le delegate définit la signature du callback — ce qui sera appelé chez les abonnés et comment. La déclaration d'un événement se fait avec le mot-clé event, et delegate — avec le mot-clé delegate.
Pourquoi les événements ?
- Couplage faible : L'éditeur ne connaît rien des abonnés — il envoie juste un signal.
- Flexibilité de l'architecture : On peut ajouter ou retirer dynamiquement des handlers sans modifier le code de l'éditeur.
- Scalabilité : On ajoute un nouvel abonné — il commence immédiatement à recevoir les notifications.
Patron classique «Éditeur-Abonné»
Imaginons que nous ayons une classe «Alarme incendie» (éditeur) et une classe «Personne dans le bâtiment» (abonné). Quand l'alarme se déclenche, elle avertit tout le monde en même temps — peu importe combien de personnes sont dans le bâtiment et où elles se trouvent. C'est le patron «Éditeur-Abonné» (ou Observer).
L'éditeur ne sait pas combien ni quels abonnés existent — il notifie simplement, et les autres s'abonnent aux notifications ou les ignorent.
Comment ça marche en C# ?
- Éditeur : définit l'événement (event), fournit l'abonnement/le désabonnement.
- Abonné : s'abonne à l'événement et implémente le handler (la méthode-handler sera appelée quand l'événement survient).
2. Événements en pratique : premier exemple
En passant de la maison au code, décrivons un modèle simple. Supposons que nous avons une application console où un objet-timer «tic-tac» chaque seconde, et différents handlers réagissent (par exemple affichent «tic» dans la console ou comptent le nombre de tics).
Étape 1. On définit le delegate et l'événement
public class SimpleTimer
{
// Déclarons le delegate pour l'événement
public delegate void TickEventHandler(object sender, EventArgs e);
// Événement basé sur le delegate
public event TickEventHandler Tick;
public void Start(int count)
{
for (int i = 0; i < count; i++)
{
System.Threading.Thread.Sleep(1000); // imitation du tic !
OnTick(); // déclencher l'événement !
}
}
protected virtual void OnTick()
{
// Appelons l'événement s'il y a des abonnés (Tick != null)
Tick?.Invoke(this, EventArgs.Empty);
}
}
Que se passe-t-il ici ?
- Le delegate TickEventHandler est défini avec la signature classique object sender, EventArgs e.
- L'événement Tick — point d'abonnement pour les handlers.
- La méthode Start simule le «tic» et appelle OnTick périodiquement.
- Dans OnTick l'événement est appelé de manière sûre : Tick?.Invoke(..., EventArgs.Empty).
Étape 2. S'abonner à l'événement
class Program
{
static void Main()
{
var timer = new SimpleTimer();
// On s'abonne à l'événement Tick
timer.Tick += Timer_Tick;
timer.Start(3);
// On peut se désabonner si nécessaire
timer.Tick -= Timer_Tick;
}
static void Timer_Tick(object sender, EventArgs e)
{
Console.WriteLine("Tic !");
}
}
On crée le timer, on s'abonne avec l'opérateur +=, puis à chaque tic le handler est appelé. Le désabonnement — opérateur -=.
3. Petites astuces utiles
Pourquoi les événements sont-ils meilleurs que des appels «durs» ?
Si SimpleTimer écrivait directement dans la console dans OnTick, la classe serait fortement couplée à une action spécifique. Les événements «libèrent» le code : le timer ne sait pas ce que vont faire les abonnés — lancer une fusée, logger dans un fichier ou envoyer un e-mail.
Différence importante entre événements et delegates
- Delegate — un «pointeur» vers une méthode, et événement — c'est un delegate avec des restrictions d'accès.
- Les abonnés peuvent seulement s'abonner/se désabonner ; appeler l'événement de l'extérieur n'est pas possible — seul l'éditeur peut le faire.
- Pour déclarer un événement, ajoutez le modificateur event au type delegate — le compilateur fournit un modèle d'accès correct.
Schéma succinct du fonctionnement d'un événement en C#
+------------------+ +------------------------------+
| | | |
| Éditeur | <------> | Abonné |
| (Publisher/Event)| | (Subscriber/Handler) |
| | | |
+------------------+ +------------------------------+
| 1) déclare l'événement | 2) s'abonne à l'événement
| 3) le déclenche | 4) implémente le handler
Quand utiliser les événements ?
- Il faut avertir un nombre indéfini d'auditeurs qu'un fait est survenu.
- On ne veut pas coupler la logique d'action à l'intérieur de la classe-source.
- UI, interaction asynchrone, notifications système — tout tourne autour des événements.
Particularités brèves de l'implémentation des événements en C#
- On ne peut pas déclencher un événement «de l'extérieur» : seul le code de l'éditeur a le droit d'appeler Invoke.
- Abonnement/désabonnement : opérateurs +=/-= ; il peut y avoir plusieurs handlers.
- Un événement est essentiellement une liste de delegates : quand l'événement survient, tous les handlers sont appelés dans l'ordre d'abonnement.
- Delegates recommandés : utilisez EventHandler et EventHandler<TEventArgs> pour la compatibilité avec l'écosystème .NET.
4. Erreurs typiques des débutants
Ils oublient de vérifier qu'il y a des abonnés : Tick != null. Il vaut mieux utiliser l'appel sûr : Tick?.Invoke(...).
Ils s'abonnent à l'événement mais ne se désabonnent pas quand le handler n'est plus nécessaire. Ça peut retenir des objets en mémoire et provoquer des fuites.
Ils essaient de «déclencher» un événement depuis une classe externe — le compilateur ne le permettra pas. On ne peut pas écrire quelque chose comme game.GameOver(), si c'est un événement et non une méthode.
Ils ne respectent pas la signature du delegate. Pour les événements utilisez les standards EventHandler ou EventHandler<TEventArgs> — ainsi le code reste compatible avec les autres bibliothèques .NET.
GO TO FULL VERSION