1. Introduction
Rappelons : un événement est un contrat public, une promesse de votre code d'"appeler" les abonnés quand quelque chose d'important se produit. Dans les exemples des cours précédents vous avez peut‑être vu une déclaration comme :
public event Action<string> MessageSent;
Ou même :
public delegate void MyHandler(int value);
public event MyHandler SomethingHappened;
Ça marche, mais cette approche génère toute une série de problèmes — à commencer par l'incompatibilité des signatures des méthodes et l'impossibilité de savoir qui a déclenché l'événement et ce qui s'est passé exactement. Imagine que sur ton clavier, à chaque pression la touche se déclenche aléatoirement, différemment à chaque fois dans le même programme pour la même action. Par exemple tu joues et tu appuies sur espace. Une seconde c'était pour sauter, maintenant ça ouvre l'inventaire. Un cauchemar, pas un standard !
Dans .NET il existe une sorte de Constitution pour les événements — c'est la convention sur la signature du handler et la structure des données transmises avec l'événement. Voici le style canonique :
void Handler(object sender, EventArgs args);
Ça te dit quelque chose ? On le retrouve partout : du click d'un bouton WinForms aux événements système d'ASP.NET, et même dans des bibliothèques tierces.
2. Qu'est-ce que EventHandler et EventArgs
L'idée principale : chaque événement rapporte deux choses :
- Qui a déclenché l'événement ? sender
- Que s'est‑il passé ? EventArgs — des données supplémentaires
En C# ça se formalise ainsi :
public delegate void EventHandler(object sender, EventArgs e);
- object sender — référence à l'initiateur de l'événement. Ça peut être n'importe quoi — simplement this.
- EventArgs e — objet contenant des informations supplémentaires sur l'événement. Pour des scénarios simples on utilise EventArgs.Empty, pour des cas plus complexes — des classes qui héritent de EventArgs.
Fait amusant
Dans les recommandations officielles .NET pour les API publiques, il est d'usage que l'événement ait la signature (object sender, EventArgs e). Si tu vois un événement sans sender et sans EventArgs — c'est probablement une simplification faite par l'auteur, pas le style canonique .NET.
Grand avantage d'un seul standard
Avec un standard unique, les événements sont plus faciles à logger, tester, s'abonner de manière universelle et étendre le système. Ouvre les sources de WinForms, WPF, ASP.NET — tu verras le même pattern partout.
3. Comment utiliser le modèle standard d'événements
1. Utiliser le delegate intégré EventHandler
Plutôt que de déclarer ton propre delegate, tu peux utiliser celui déjà fourni :
public event EventHandler SmthHappened;
Et maintenant le handler ressemble toujours à la même chose :
private void OnSmthHappened(object sender, EventArgs e)
{
// Logique de réaction à l'événement
}
L'abonnement reste simple :
myObj.SmthHappened += OnSmthHappened;
Important : si l'événement ne transmet pas de données supplémentaires, utilise EventArgs.Empty.
2. Créer ses propres arguments d'événement
Si tu dois passer des infos (résultat d'un calcul, nom de fichier, erreur), crée une classe qui hérite de EventArgs :
public class CalculationEventArgs : EventArgs
{
public double Result { get; }
public CalculationEventArgs(double result) => Result = result;
}
Puis utilise le delegate générique — EventHandler<TEventArgs> :
public event EventHandler<CalculationEventArgs> CalculationFinished;
Et le handler reçoit maintenant ton type d'arguments :
private void OnCalculationFinished(object sender, CalculationEventArgs e)
{
Console.WriteLine($"Le calcul est terminé. Résultat : {e.Result}");
}
4. Exemple d'application
On développe notre projet pédagogique — supposons qu'on ait une calculatrice qui additionne ou soustrait des nombres et notifie la fin de l'opération via un événement.
Exemple minimaliste
public class Calculator
{
public event EventHandler<CalculationEventArgs> CalculationPerformed;
public void Add(int a, int b)
{
int result = a + b;
// "On tire" l'événement
CalculationPerformed?.Invoke(this, new CalculationEventArgs(result));
}
}
public class CalculationEventArgs : EventArgs
{
public int Result { get; }
public CalculationEventArgs(int result) => Result = result;
}
On s'abonne et on utilise :
var calc = new Calculator();
calc.CalculationPerformed += (sender, e) =>
{
Console.WriteLine($"Résultat de l'opération : {e.Result}");
};
calc.Add(10, 20);
// Affichera : Résultat de l'opération : 30
Voilà le "standard" : le handler reçoit toujours l'objet expéditeur et l'objet d'arguments — universel et clair.
5. Nuances utiles
Encapsulation de l'appel d'événement : bon style
Dans .NET on a l'habitude d'extraire la logique d'appel d'un événement dans une méthode protégée préfixée par On :
protected virtual void OnCalculationPerformed(CalculationEventArgs e)
{
CalculationPerformed?.Invoke(this, e);
}
Et dans la logique (« Add », « Subtract », etc.) on appelle simplement cette méthode :
public void Add(int a, int b) => OnCalculationPerformed(new CalculationEventArgs(a + b));
Ce style permet aux classes dérivées de redéfinir le comportement de l'événement et réduit le risque d'oublier de l'appeler.
Schéma : Comment est structuré un événement selon le standard .NET
graph LR
A[Objet-éditeur] -- "event EventHandler/ EventHandler<TEventArgs>" --> B[Liste d'abonnés]
B -- "Méthode-handler (object sender, EventArgs e)" --> C[Réaction à l'événement]
A -- "this (expéditeur)" --> C
A -- "EventArgs (données)" --> C
Comparaison des variantes de déclaration d'événements
| Approche | Transmission de l'initiateur (sender) | Transmission des arguments | Universalité | Usage dans .NET |
|---|---|---|---|---|
|
Non | Oui | Faible | Non |
|
Oui | Oui | Moyenne | Non (rarement) |
|
Oui | Non (EventArgs) | Élevée | Oui, standard |
|
Oui | Oui (MyArgs) | Très élevée | Oui, standard |
Utilité pratique pour les entretiens et le code en production
Utiliser le modèle standard d'événements est un must-have pour un développeur .NET. Lors d'un entretien on te demandera presque à coup sûr à propos de EventHandler et du duo sender/EventArgs. Un événement de type Action<T> est souvent perçu comme une "simplification".
Dans les vrais projets cette approche facilite le travail en équipe, les tests, l'extensibilité et la maintenance. Les bibliothèques tierces (logging, profilers) s'intègrent plus facilement quand on utilise la forme standard.
Organigramme d'appel d'événement
flowchart TD
subgraph A[Classe-éditeur]
C1((Objet))
C2[Méthode qui déclenche l'événement]
C3["event EventHandler<MyArgs>"]
end
subgraph B[Classe-abonné]
D1((Abonnement))
D2["Handler d'événement (object sender, MyArgs args)"]
end
C1 -- Appelle --> C2
C2 -- "Invoke(this, args)" --> C3
C3 -- "Notifie" --> D1
D1 -- "Appelle" --> D2
6. Conseils, nuances et erreurs typiques
1. Signature du handler. Tu as envie de faire un événement de type Action<int> ? C'est tentant, mais tu perds le sender et la compatibilité avec l'écosystème.
2. Transmission des arguments. Ne confonds pas EventArgs avec des paramètres ordinaires. Place toutes les données pour le handler dans l'objet d'arguments.
3. Utiliser null pour les arguments. Au lieu de null utilise EventArgs.Empty quand il n'y a pas de données supplémentaires.
4. Typage faible. N'en fais pas un événement "omnivore" avec EventHandler où tu balances tout. Crée une classe dérivée dédiée pour chaque événement — meilleure lisibilité et robustesse.
5. Erreurs dans l'appel de l'événement. Vérifie toujours la présence d'abonnés : SomeEvent?.Invoke(this, e). Sans abonnés la référence d'événement vaut null.
6. Violation de l'encapsulation. N'appelle pas l'événement depuis l'extérieur de la classe-éditeur. L'événement sert uniquement à s'abonner/se désabonner ; l'appel doit se faire à l'intérieur via une méthode On....
GO TO FULL VERSION