1. Introduzione
Quindi, quando parliamo di «evento» (event) nel contesto di C#, ci riferiamo a un meccanismo che permette di notificare in modo sicuro uno o più oggetti che qualcosa è successo. Gli eventi non sono magia: sotto il cofano sono delegati (la keyword delegate) con protezioni aggiuntive contro usi impropri.
Immagina l'iscrizione a un canale YouTube. Il canale (l'oggetto proprietario dell'evento) ha un pulsante "Iscriviti" — qualsiasi spettatore (un altro oggetto) può premerlo ed entrare nella lista degli iscritti (i delegati-handler). Quando l'autore pubblica un nuovo video (solleva l'evento), solo gli iscritti ricevono la notifica, e solo il proprietario del canale decide quando succede. Dettaglio importante: gli spettatori possono iscriversi o cancellarsi, ma non possono inviare la notifica da soli — quel diritto spetta solo al proprietario del canale.
Un po' di sintassi formale
// Dichiarazione di un delegate che definirà la "forma" dell'handler dell'evento
public delegate void SimpleEventHandler();
// Dichiarazione di un evento basato sul delegate
public event SimpleEventHandler SomethingHappened;
È semplice: event è la keyword per dichiarare un evento, e il tipo dell'evento è sempre definito da un delegate.
2. Primo esempio semplice: evento "Pulsante premuto!"
Passo 1. Definiamo il delegate per gli handler dell'evento
// Handler dell'evento: non ritorna nulla e non accetta parametri
public delegate void ButtonClickHandler();
Passo 2. Classe con evento
public class Button
{
// Evento: ci si può iscrivere e cancellare, ma non è possibile sollevarlo dall'esterno
public event ButtonClickHandler Click;
// Metodo che "premere il pulsante"
public void Press()
{
Console.WriteLine("Pulsante premuto!");
// Chiamata dell'evento: notificare tutti gli iscritti
Click?.Invoke();
}
}
Nota: l'evento è dichiarato basandosi sul delegate, e nel metodo Press l'evento viene invocato con ?.Invoke(). Perché? Perché l'evento può essere vuoto (nessuno si è iscritto), quindi Click è null. L'operatore di chiamata sicura garantisce che gli handler vengano eseguiti solo se ci sono iscritti.
Passo 3. Sottoscrizione all'evento ed esecuzione del codice
// Esempio d'uso
public class Program
{
public static void Main()
{
var button = new Button();
// Aggiungiamo un "listener" (ci iscriviamo all'evento)
button.Click += OnButtonClicked;
button.Click += () => Console.WriteLine("Un altro handler!");
button.Press(); // Simuliamo la pressione del pulsante
// Possiamo cancellare l'iscrizione all'evento
button.Click -= OnButtonClicked;
button.Press();
}
// Handler normale dell'evento
public static void OnButtonClicked()
{
Console.WriteLine("Handler: Il pulsante è stato premuto!");
}
}
Alla prima pressione verranno eseguiti entrambi gli handler, alla seconda solo la lambda.
3. Differenze tra evento e delegate
A un delegate si possono assegnare liberamente valori e persino sostituire completamente la lista degli iscritti — qualcuno potrebbe scrivere qualcosa come button.Click = null e tutte le iscrizioni precedenti verrebbero "cancellate".
Con un evento è diverso — è più strettamente legato all'oggetto. Solo il proprietario della classe in cui l'evento è dichiarato può invocarlo direttamente (per esempio Click() dall'interno della classe). Qualsiasi altro codice può solo iscriversi tramite += o cancellare l'iscrizione tramite -=, ma non può "azzerare" tutto in una volta. Questo protegge l'incapsulamento e impedisce di "rompere" il sistema di sottoscrizioni.
4. Firma: tipi di delegate per eventi, parametri
La convenzione in .NET è che un handler di eventi riceva due parametri — object sender (chi ha sollevato l'evento) e gli argomenti dell'evento (EventArgs). Si raccomanda di usare i delegate EventHandler o EventHandler<TEventArgs>.
Esempio: delegate con parametri
public delegate void ButtonClickHandler(object sender, EventArgs e);
EventArgs è la classe base per passare informazioni aggiuntive. Se servono più dati, creiamo una propria classe derivata.
Applichiamolo al nostro pulsante
// Classe degli argomenti dell'evento
public class ButtonClickEventArgs : EventArgs
{
public string UserName { get; }
public ButtonClickEventArgs(string userName)
{
UserName = userName;
}
}
public class Button
{
public event EventHandler<ButtonClickEventArgs> Click;
public void Press(string userName)
{
Console.WriteLine("Pulsante premuto!");
Click?.Invoke(this, new ButtonClickEventArgs(userName));
}
}
public static void Main()
{
var button = new Button();
button.Click += OnButtonClicked;
button.Press("Vasiliy");
}
public static void OnButtonClicked(object sender, ButtonClickEventArgs e)
{
Console.WriteLine($"L'utente {e.UserName} ha premuto il pulsante!");
}
5. Schema visivo: come funziona un evento
+-------------+
| Utente |
+-------------+
|
v
+--------------+
| Button.Press|
+--------------+
|
v
+-----------------+ +------------------------+
| Chiamata Click?|----->---| Iscritto 1 |
+-----------------+ +------------------------+
| | Eseguire handler |
v +------------------------+
+-----------------+ +------------------------+
| Se c'è un |----->---| Iscritto 2 |
| iscritto | +------------------------+
: | Eseguire handler |
v +------------------------+
— Button.Press invoca l'evento; gli handler degli iscritti vengono eseguiti in ordine.
6. Sfumature utili
Forma raccomandata per gli eventi in .NET
Usa EventHandler e EventHandler<TEventArgs> per mantenere compatibilità con librerie e strumenti. Questo approccio rende più facile evolvere gli eventi: aggiungi nuove proprietà ai tuoi EventArgs senza rompere gli iscritti.
Documentazione: EventHandler, event.
Eventi vs delegate
Se devi collegare un componente a un metodo specifico — un delegate può bastare (delegate). Se invece vuoi permettere a diverse parti del programma di iscriversi e cancellarsi in qualsiasi momento — usa un evento (event).
7. Errori tipici e situazioni imbarazzanti quando crei eventi
Errore №1: invocare l'evento senza controllare null. Se non ci sono iscritti, un tentativo diretto di invocare l'evento causerà una NullReferenceException. Usa la chiamata sicura:
Click?.Invoke(...);
Errore №2: tentare di invocare l'evento dall'esterno della classe. Un evento può essere sollevato (invocato) solo all'interno della classe dove è dichiarato. Da un'altra classe il compilatore segnalerà un errore — questo protegge l'incapsulamento.
Errore №3: iscrizione multipla dello stesso handler. Se lo stesso handler è iscritto più volte, verrà chiamato tante volte. Questa è una caratteristica del meccanismo di sottoscrizione. Fai attenzione ai duplicati con += e a rimuovere correttamente con -=.
GO TO FULL VERSION