1. Style de déclaration et de nommage des événements
Les événements ne sont pas de simples delegates. C'est une entité distincte pour la communication entre parties de l'application, et sa déclaration doit être claire.
Utilisez le bon type de delegate
Dans 99% des cas utilisez les delegates standard :
- EventHandler — pour les événements sans données.
- EventHandler<TEventArgs> — quand il faut passer des paramètres.
La standardisation facilite la maintenance du code et l'intégration avec les bibliothèques .NET. N'inventez pas votre propre delegate si EventHandler convient.
public event EventHandler SomethingHappened; // Pas de données
public event EventHandler<MyEventArgs> DataReceived; // Données supplémentaires présentes
Si une personnalisation particulière est nécessaire — déclarez votre propre delegate, mais c'est rare.
Nommage des événements
En .NET les événements sont nommés au passé : Completed, Clicked, Changed, Received. Cela souligne le fait accompli.
Exemples :
public event EventHandler DataLoaded; // Les données ont été chargées
public event EventHandler<MessageEventArgs> MessageReceived; // Message reçu
public event EventHandler Saving; // Le processus de sauvegarde a commencé
Parfois on utilise la forme Changing pour les événements "avant" le changement, pour donner une chance d'intervenir.
2. Organisation de la classe publisher : méthode virtuelle OnEvent
Ajoutez toujours une méthode protégée virtuelle qui déclenche l'événement : point central d'appel, extensibilité via l'héritage et comportement prévisible.
public class FileLoader
{
public event EventHandler<FileLoadedEventArgs> FileLoaded;
protected virtual void OnFileLoaded(FileLoadedEventArgs e)
{
FileLoaded?.Invoke(this, e);
}
public void Load(string filename)
{
// ... logique de chargement de fichier ...
OnFileLoaded(new FileLoadedEventArgs(filename));
}
}
public class FileLoadedEventArgs : EventArgs
{
public string FileName { get; }
public FileLoadedEventArgs(string fileName) => FileName = fileName;
}
Que seule la méthode OnFileLoaded invoque l'événement — c'est plus simple à maintenir et à tester.
3. Règles d'abonnement et de désabonnement : cycle de vie, IDisposable
Si la durée de vie de l'abonné est plus courte que celle du publisher, désabonnez-vous obligatoirement avant la destruction de l'abonné. Il est pratique d'implémenter IDisposable et de se désabonner dans Dispose().
public class TemporaryListener : IDisposable
{
private readonly Publisher _publisher;
public TemporaryListener(Publisher publisher)
{
_publisher = publisher;
_publisher.DataReceived += HandleData;
}
private void HandleData(object sender, EventArgs e)
{
// Traitement des données
}
public void Dispose()
{
_publisher.DataReceived -= HandleData;
}
}
// Utilisation avec using:
using (var listener = new TemporaryListener(myPublisher))
{
// le listener écoute les événements ici
}
// Après être sorti du using - Dispose est appelé, désabonnement effectué
Si vous oubliez de vous désabonner, le publisher gardera la référence au delegate de l'abonné — fuite de mémoire et "objets zombie".
4. Appel thread-safe des événements
Dans du code multithread, les abonnés peuvent être ajoutés/supprimés en parallèle à l'appel de l'événement. Cela mène à des races et à des NullReferenceException. Utilisez le pattern thread-safe : copiez le delegate dans une variable locale.
protected virtual void OnSomethingHappened()
{
EventHandler handler = SomethingHappened;
handler?.Invoke(this, EventArgs.Empty);
}
Avec C# 6+ c'est suffisant :
SomethingHappened?.Invoke(this, EventArgs.Empty);
5. Utiliser EventArgs au lieu de object
Ne passez pas de données via object ou des champs de classe. Utilisez un typage strict via des héritiers de EventArgs.
public class DownloadCompletedEventArgs : EventArgs
{
public string FileName { get; }
public long Size { get; }
public DownloadCompletedEventArgs(string fileName, long size)
{
FileName = fileName;
Size = size;
}
}
public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted;
6. Documenter les événements et les abonnés
Documentez : quand l'événement est déclenché, le sens des champs de EventArgs, si un désabonnement est nécessaire et quand.
/// <summary>
/// L'événement survient après un chargement de données réussi.
/// </summary>
public event EventHandler<DataLoadedEventArgs> DataLoaded;
7. Recommandations d'ensemble pour l'architecture des événements
Séparez les responsabilités
Le publisher ne fait que notifier du fait. L'abonné décide lui-même quand s'abonner et se désabonner.
Évitez le "spamming" d'événements
Ne générez pas le même événement des dizaines de fois par seconde sans besoin — surcharge inutile.
Évitez d'utiliser les événements pour une communication bidirectionnelle
Les événements conviennent pour le schéma "un informe — beaucoup écoutent". Pour une communication bidirectionnelle considérez des interfaces, des callbacks ou d'autres mécanismes.
Ne conservez pas dans la classe des références explicites aux abonnés
Ne retenez pas de références explicites aux abonnés — les événements et delegates s'en chargent automatiquement.
8. Antipatterns classiques
Événements sans typage
public event Action<object> SomethingHappened; // On ne sait pas ce qu'il y a dedans
Mauvais : le typage est cassé, des casts sont nécessaires, la maintenabilité est perdue.
Oubli de désabonnement
public class ShortLivedListener
{
public ShortLivedListener(Publisher p) =>
p.DataReceived += DoWork;
private void DoWork(object sender, EventArgs e) { /* ... */ }
// Pas de Dispose, pas de désabonnement => objets zombie !
}
Violation du SRP
La classe est à la fois publisher, abonné et handler — mélange de responsabilités. Séparez les responsabilités.
9. Application pratique lors d'entretiens et dans les projets
Dans beaucoup de projets basés sur publish-subscribe, une bonne organisation des événements est la clé de la scalabilité et de la maintenabilité. Aux entretiens on demande souvent :
- implémenter un système d'événements avec le bon typage,
- montrer la gestion du cycle de vie des abonnés,
- expliquer l'appel thread-safe des événements.
Un code événementiel propre, documenté et correctement organisé vous fera ressortir parmi les candidats.
GO TO FULL VERSION