1. Stile di dichiarazione e denominazione degli eventi
Gli eventi non sono semplici delegate. Sono un'entità separata per la comunicazione tra parti dell'applicazione, e la loro dichiarazione deve essere chiara.
Usa il tipo di delegate corretto
Nel 99% dei casi usa i delegate standard:
- EventHandler — per eventi senza dati.
- EventHandler<TEventArgs> — quando serve passare parametri.
La standardizzazione facilita la manutenzione del codice e l'integrazione con le librerie .NET. Non inventare un delegate custom se va bene EventHandler.
public event EventHandler SomethingHappened; // Nessun dato
public event EventHandler<MyEventArgs> DataReceived; // Ci sono dati aggiuntivi
Se serve una personalizzazione particolare — dichiara un delegate tuo, ma è raro.
Denominazione degli eventi
In .NET gli eventi si chiamano al passato: Completed, Clicked, Changed, Received. Questo sottolinea il fatto che è successo qualcosa.
Esempi:
public event EventHandler DataLoaded; // I dati sono stati caricati
public event EventHandler<MessageEventArgs> MessageReceived; // Messaggio ricevuto
public event EventHandler Saving; // Il processo di salvataggio è iniziato
A volte si usa la forma Changing per eventi "prima" della modifica, per dare la possibilità di intervenire.
2. Organizzazione della classe publisher: metodo virtuale OnEvent
Aggiungi sempre un metodo protected virtual che invoca l'evento: punto centrale di invocazione, estendibilità tramite ereditarietà e comportamento prevedibile.
public class FileLoader
{
public event EventHandler<FileLoadedEventArgs> FileLoaded;
protected virtual void OnFileLoaded(FileLoadedEventArgs e)
{
FileLoaded?.Invoke(this, e);
}
public void Load(string filename)
{
// ... logica di caricamento file ...
OnFileLoaded(new FileLoadedEventArgs(filename));
}
}
public class FileLoadedEventArgs : EventArgs
{
public string FileName { get; }
public FileLoadedEventArgs(string fileName) => FileName = fileName;
}
Fai in modo che solo OnFileLoaded invochi l'evento — così è più semplice mantenere e testare.
3. Regole di sottoscrizione e desottoscrizione: ciclo di vita, IDisposable
Se la vita del subscriber è più breve di quella del publisher, assicurati di desottoscriverti prima della distruzione del subscriber. È comodo implementare IDisposable e desottoscrivere in 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)
{
// Lavoro con i dati
}
public void Dispose()
{
_publisher.DataReceived -= HandleData;
}
}
// Uso con using:
using (var listener = new TemporaryListener(myPublisher))
{
// listener ascolta gli eventi qui
}
// Dopo l'uscita dal using - Dispose è stato chiamato, avviene la desottoscrizione
Se dimentichi di desottoscriverti, il publisher terrà il riferimento al delegate del subscriber — avrai memory leak e "oggetti zombi".
4. Invocazione thread-safe degli eventi
Nel codice multithread i subscriber possono essere aggiunti/rimossi in parallelo all'invocazione dell'evento. Questo può causare race e NullReferenceException. Usa il pattern thread-safe: copia il delegate in una variabile locale.
protected virtual void OnSomethingHappened()
{
EventHandler handler = SomethingHappened;
handler?.Invoke(this, EventArgs.Empty);
}
Con C# 6+ è sufficiente:
SomethingHappened?.Invoke(this, EventArgs.Empty);
5. Usa EventArgs invece di object
Non passare dati tramite object o campi di classe. Usa la tipizzazione forte tramite classi derivate da 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. Documentare eventi e subscriber
Documenta: quando viene invocato l'evento, il significato dei campi di EventArgs, se è necessario desottoscriversi e quando.
/// <summary>
/// L'evento scatta dopo il successo del caricamento dei dati.
/// </summary>
public event EventHandler<DataLoadedEventArgs> DataLoaded;
7. Raccomandazioni architetturali sugli eventi
Separa le responsabilità
Il publisher deve solo notificare il fatto. Il subscriber decide autonomamente quando sottoscriversi e desottoscriversi.
Evita di "sparare" eventi in continuazione
Non generare lo stesso evento decine di volte al secondo senza bisogno — è un carico superfluo.
Evita usare eventi per comunicazione bidirezionale
Gli eventi sono per lo schema "uno notifica — molti ascoltano". Per comunicazione bidirezionale considera interfacce, callback o altri meccanismi.
Non mantenere riferimenti espliciti ai subscriber nella classe
Non tenere riferimenti espliciti ai subscriber — eventi e delegate lo fanno automaticamente.
8. Anti-pattern classici
Eventi non tipizzati
public event Action<object> SomethingHappened; // Non è chiaro cosa c'è dentro
Male: viene spezzata la tipizzazione, servono cast, si perde manutenibilità.
Dimenticare la desottoscrizione
public class ShortLivedListener
{
public ShortLivedListener(Publisher p) =>
p.DataReceived += DoWork;
private void DoWork(object sender, EventArgs e) { /* ... */ }
// Nessun Dispose, nessuna desottoscrizione => oggetti zombi!
}
Violazione di SRP
La classe è contemporaneamente publisher, subscriber e handler — mescolanza di ruoli. Separa le responsabilità.
9. Applicazioni pratiche in colloqui e progetti
In molti progetti basati su publish-subscribe una buona organizzazione degli eventi è fondamentale per scalabilità e manutenzione. Nei colloqui spesso chiedono di:
- implementare un sistema di eventi con corretta tipizzazione,
- mostrare la gestione del ciclo di vita dei subscriber,
- spiegare l'invocazione thread-safe degli eventi.
Codice di eventi pulito, documentato e ben organizzato ti fa subito distinguere tra i candidati.
GO TO FULL VERSION