CodeGym /Corsi /C# SELF /Analisi dettagliata della sottoscrizione (

Analisi dettagliata della sottoscrizione ( +=)

C# SELF
Livello 53 , Lezione 0
Disponibile

1. Introduzione

Gli eventi in C# non sono semplici variabili dove assegnare un delegate. Sono una lista protetta di handler, e solo il proprietario dell'evento può avviarne l'esecuzione; gli altri possono solo aggiungere (+=) o rimuovere (-=) le loro reazioni.

Per esempio, così ci si può iscrivere a un evento:

worker.WorkCompleted += Worker_WorkCompleted;

A prima vista sembra una semplice somma, ma in realtà funziona diversamente. Sotto il cofano l'evento mantiene una invocation list — un insieme di delegate da chiamare quando l'evento scatta. Quando scrivi +=, un nuovo handler viene aggiunto a questa lista.

Vediamo più nel dettaglio cosa succede internamente, come si forma questa catena e quali sottigliezze possono emergere durante la sottoscrizione.

Cos'è la catena di delegate?

Ricordiamo che i delegate in C# sono "multicast": possono avere più metodi assegnati e tutti verranno eseguiti in ordine se il delegate viene invocato. Gli eventi usano questo meccanismo: il loro valore è, in sostanza, un delegate con una lista di handler.

In termini di codice:

public event EventHandler<WorkCompletedEventArgs> WorkCompleted;

Quando qualcuno si iscrive:

worker.WorkCompleted += MyHandler;

Il C# sotto il cofano fa più o meno così:

  • Prende il delegate corrente (la lista di handler).
  • Chiama su di esso Delegate.Combine (per unire gli handler).
  • Scrive la catena aggiornata di nuovo nella variabile dell'evento.

Schematicamente:

Operazione Lista interna di handler dell'evento
Prima null o [Handler1]
Dopo += Handler2 [Handler1, Handler2]
Dopo ancora += H3 [Handler1, Handler2, Handler3]

Curiosità: il meccanismo dei multicast delegate non è "magia", ma una implementazione concreta: sotto il cofano il delegate contiene un array di metodi da chiamare.

2. Come funziona la sottoscrizione: spiegazione semplice

Vediamo tutto con un esempio concreto. Abbiamo un publisher (Worker) e dei subscriber (Logger, Notifier):

public class Worker
{
    public event EventHandler<WorkCompletedEventArgs> WorkCompleted;

    public void DoWork()
    {
        // ... lavoro ...
        OnWorkCompleted("Compito completato!");
    }

    protected virtual void OnWorkCompleted(string message)
    {
        WorkCompleted?.Invoke(this, new WorkCompletedEventArgs { Message = message });
    }
}

public class Logger
{
    public void LogWorkCompleted(object? sender, WorkCompletedEventArgs e)
    {
        Console.WriteLine("Log: " + e.Message);
    }
}

public class Notifier
{
    public void ShowNotification(object? sender, WorkCompletedEventArgs e)
    {
        Console.WriteLine("Notifica: " + e.Message);
    }
}

Nel Main:

var worker = new Worker();
var logger = new Logger();
var notifier = new Notifier();

worker.WorkCompleted += logger.LogWorkCompleted;
worker.WorkCompleted += notifier.ShowNotification;

// Avvio del lavoro
worker.DoWork();

Quando viene chiamato OnWorkCompleted, l'evento prima invoca logger.LogWorkCompleted, poi notifier.ShowNotification (nell'ordine in cui ti sei iscritto).

3. Dettagli utili

Visualizzazione: come l'evento conserva gli handler

+---------------------+
| Worker              |
|---------------------|
| WorkCompleted Event |    ---> [ LogWorkCompleted, ShowNotification ]
+---------------------+

Ad ogni sottoscrizione un nuovo handler "si attacca" alla lista esistente. Quando l'evento viene invocato, il delegate chiama in sequenza tutti i metodi sottoscritti.

Sottoscrizione multipla dello stesso metodo

worker.WorkCompleted += logger.LogWorkCompleted;
worker.WorkCompleted += logger.LogWorkCompleted; // Due volte!

In questo caso l'handler verrà chiamato tante volte quante sono le sottoscrizioni — quindi qui due volte consecutive.

Sottoscrivere con una lambda

worker.WorkCompleted += (sender, e) => Console.WriteLine("Handler anonimo: " + e.Message);

Se iscrivi la stessa lambda più volte, sarà chiamata più volte ad ogni evento. Però attenzione: ogni lambda è un oggetto-delegate diverso, quindi non è banale rimuoverla (vedi lezione 260 e seguenti per dettagli).

Cosa succede sotto: analisi a basso livello

L'evento è una proprietà speciale con due accessor (add/remove) chiamati quando usi += e -=. Semplificando, il compilatore genera più o meno questo codice:

// Più o meno così (semplificato)
public event EventHandler<WorkCompletedEventArgs> WorkCompleted
{
    add { /* codice per aggiungere l'handler */ }
    remove { /* codice per rimuovere l'handler */ }
}

Di default si usa l'implementazione standard: il delegate viene combinato con Delegate.Combine e rimosso con Delegate.Remove.

Questo protegge l'evento: dall'esterno non puoi invocarlo direttamente (niente worker.WorkCompleted(...);), puoi solo iscriverti o cancellare la sottoscrizione.

Meccanica della sottoscrizione: schema

             +----------------------+
             |                      |
             v                      v
    +--------------------+   +----------------------+
    | LogWorkCompleted   |   | ShowNotification    |
    +--------------------+   +----------------------+
             ^                      ^
             \______________________/
                       ^
                       |
               WorkCompleted Event

La cosiddetta "Invocation List" — la catena delle chiamate.

Perché è importante: rilevanza pratica

Capire la meccanica delle sottoscrizioni è fondamentale per gestire i legami tra oggetti. Puoi costruire sistemi complessi dove i componenti si iscrivono e si cancellano dinamicamente dagli eventi senza creare dipendenze rigide. È lo standard in UI frameworks, game engine, server app e perfino in architetture moderne (lì però la cosa è spesso a livello di queue, ma l'idea è la stessa).

Ai colloqui spesso chiedono come funziona il modello eventi in C#, perché rende il sistema flessibile e come gestire correttamente il ciclo di vita delle sottoscrizioni.

4. Cosa si può (e non si può) fare dall'esterno della classe

Permesso

  • Iscriversi a un evento (+=)
  • Cancellare la sottoscrizione (-=)

Vietato

  • Invocare l'evento direttamente
  • Assegnare direttamente un delegate all'evento (worker.WorkCompleted = ... — errore!)

Queste restrizioni sono realizzate tramite la keyword event. Se dichiarassi il delegate come campo normale:

public EventHandler<WorkCompletedEventArgs> WorkCompleted; // non event!

— chiunque potrebbe fare qualsiasi cosa, inclusa azzerare gli handler, causando caos e possibili bug. Per questo quasi sempre è meglio usare solo event!

Si può iscrivere lo stesso metodo a più eventi?

Sì! Questo si chiama "multisubscribe". Per esempio:

worker.WorkCompleted += logger.LogWorkCompleted;
anotherWorker.WorkCompleted += logger.LogWorkCompleted;

Se hai iscritto lo stesso metodo a eventi di oggetti diversi, l'handler verrà chiamato per entrambi gli eventi. Dentro l'handler capisci chi ha scatenato l'evento tramite il parametro sender.

Caso reale: sottoscrizioni dinamiche

Immagina un'app dove l'utente può lanciare più task in parallelo. Per ogni task si crea un oggetto Worker, al cui evento si iscrive lo stesso handler — per esempio logger.LogWorkCompleted.

var allWorkers = new List<Worker>();
for (int i = 0; i < 10; i++)
{
    var w = new Worker();
    w.WorkCompleted += logger.LogWorkCompleted;
    allWorkers.Add(w);
}

Di conseguenza, quando qualsiasi task termina, il logger riceve la notifica e registra cosa e quando è successo.

Pratica: come vedere gli iscritti a un evento

Nel codice normale non puoi sapere direttamente quanti handler sono iscritti (l'evento incapsula il delegate). Però dentro la classe-publisher puoi lavorare col delegate dell'evento direttamente, per esempio usando GetInvocationList():

// Solo dentro la classe-publisher
var handlers = WorkCompleted?.GetInvocationList();
if (handlers != null)
    Console.WriteLine($"Iscritti: {handlers.Length}");

Questo è utile per log o logiche di invio non standard (ma usalo soprattutto a scopo didattico).

5. Errori tipici e particolarità

Handler non aggiunto? Non verrà chiamato!
Se non ti sei iscritto, il handler non verrà mai invocato. Prima di invocare l'evento è sempre buona pratica controllare il null (altrimenti ottieni una NullReferenceException).

Sottoscrizione ripetuta
Se ti iscrivi più volte allo stesso evento con lo stesso metodo, l'handler verrà eseguito tante volte quante sono le iscrizioni. A volte è una trappola: un += accidentale può trasformare una singola notifica in molteplici identiche.

Metodi statici/istanza
Puoi iscrivere sia metodi statici che d'istanza. L'importante è che la signature sia corretta.

public static void StaticHandler(object? sender, WorkCompletedEventArgs e) { /* ... */ }
worker.WorkCompleted += StaticHandler;

Lambda e iscrizione in un ciclo
Se per esempio in un ciclo ti iscrivi con lambda, assicurati di capire cosa succede con le variabili catturate. Si può facilmente catturare il valore sbagliato rispetto a quello che ti aspetti.

2
Compito
C# SELF, livello 53, lezione 0
Bloccato
Sottoscrizione dinamica e visualizzazione degli iscritti
Sottoscrizione dinamica e visualizzazione degli iscritti
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION