AGGIORNARE
A partire da C # 6, la risposta a questa domanda è:
SomeEvent?.Invoke(this, e);
Spesso ascolto / leggo i seguenti consigli:
Fai sempre una copia di un evento prima di controllarlo null
e attivarlo. Ciò eliminerà un potenziale problema con il threading in cui l'evento diventa null
nella posizione giusta tra dove si verifica la presenza di null e dove si genera l'evento:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
Aggiornato : dalla lettura delle ottimizzazioni ho pensato che ciò potrebbe richiedere la volatilità del membro dell'evento, ma Jon Skeet afferma nella sua risposta che il CLR non ottimizza la copia.
Ma nel frattempo, per far sì che questo problema si verifichi, un altro thread deve aver fatto qualcosa del genere:
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
La sequenza effettiva potrebbe essere questa miscela:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
Il punto è che OnTheEvent
corre dopo che l'autore ha annullato l'iscrizione, eppure ha appena annullato l'iscrizione in modo specifico per evitare che ciò accada. Sicuramente ciò che è veramente necessario è un'implementazione di eventi personalizzati con un'adeguata sincronizzazione negli accessor add
e remove
. Inoltre, esiste il problema di possibili deadlock se si tiene un blocco mentre viene generato un evento.
Quindi è questa programmazione Cult Cargo ? Sembra in questo modo: molte persone devono fare questo passo per proteggere il loro codice da più thread, quando in realtà mi sembra che gli eventi richiedano molta più attenzione di questo prima che possano essere utilizzati come parte di un design multi-thread . Di conseguenza, le persone che non stanno prestando ulteriore attenzione potrebbero anche ignorare questo consiglio: semplicemente non è un problema per i programmi a thread singolo e, in effetti, data l'assenza della volatile
maggior parte del codice di esempio online, il consiglio potrebbe non avere effetto a tutti.
(E non è molto più semplice assegnare il vuoto delegate { }
sulla dichiarazione del membro in modo da non dover mai cercare null
in primo luogo?)
aggiornato:Nel caso in cui non fosse chiaro, ho colto l'intenzione del consiglio: evitare un'eccezione di riferimento nulla in ogni circostanza. Il mio punto è che questa particolare eccezione di riferimento null può verificarsi solo se un altro thread sta cancellando dall'evento e l'unico motivo per farlo è quello di garantire che non vengano ricevute ulteriori chiamate tramite quell'evento, che NON è chiaramente raggiunto da questa tecnica . Nasconderesti una condizione di razza - sarebbe meglio rivelarla! Tale eccezione nulla aiuta a rilevare un abuso del componente. Se vuoi che il tuo componente sia protetto dagli abusi, potresti seguire l'esempio di WPF: memorizzare l'ID del thread nel costruttore e quindi generare un'eccezione se un altro thread tenta di interagire direttamente con il componente. Oppure implementa un componente veramente thread-safe (non un compito facile).
Quindi sostengo che semplicemente fare questo idioma di copia / controllo sia una programmazione di culto del carico, aggiungendo confusione e rumore al tuo codice. Per proteggere effettivamente contro altri thread richiede molto più lavoro.
Aggiornamento in risposta ai post del blog di Eric Lippert:
Quindi c'è una cosa importante che mi mancava per i gestori di eventi: "I gestori di eventi devono essere robusti di fronte al fatto di essere chiamati anche dopo che l'evento è stato cancellato", e ovviamente quindi dobbiamo solo preoccuparci della possibilità dell'evento essere delegato null
. Tale requisito sui gestori di eventi è documentato ovunque?
E così: "Esistono altri modi per risolvere questo problema; ad esempio, inizializzare il gestore in modo che un'azione vuota non venga mai rimossa. Ma fare un controllo nullo è il modello standard".
Quindi l'unico frammento rimasto della mia domanda è, perché è esplicito-null-check il "modello standard"? L'alternativa, assegnando il delegato vuoto, richiede solo = delegate {}
di essere aggiunta alla dichiarazione dell'evento, e questo elimina quelle piccole pile di puzzolenti cerimonie da ogni luogo in cui l'evento viene generato. Sarebbe facile assicurarsi che il delegato vuoto sia economico da istanziare. O mi manca ancora qualcosa?
Sicuramente deve essere che (come suggerì Jon Skeet) questo è solo un consiglio .NET 1.x che non si è spento, come avrebbe dovuto fare nel 2005?
EventName(arguments)
invocare il delegato dell'evento incondizionatamente, piuttosto che farlo invocare il delegato solo se non nullo (non fare nulla se nullo).