Aumentare gli eventi C # con un metodo di estensione – è sbagliato?

Conosciamo tutti l’horror che è la dichiarazione dell’evento in C #. Per garantire la sicurezza del thread, lo standard è scrivere qualcosa come questo :

public event EventHandler SomethingHappened; protected virtual void OnSomethingHappened(EventArgs e) { var handler = SomethingHappened; if (handler != null) handler(this, e); } 

Recentemente in qualche altra domanda su questo forum (che non riesco a trovare ora), qualcuno ha sottolineato che i metodi di estensione potrebbero essere usati bene in questo scenario. Ecco un modo per farlo:

 static public class EventExtensions { static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e) { var handler = @event; if (handler != null) handler(sender, e); } static public void RaiseEvent(this EventHandler @event, object sender, T e) where T : EventArgs { var handler = @event; if (handler != null) handler(sender, e); } } 

Con questi metodi di estensione, tutto ciò che devi dichiarare e generare un evento è qualcosa del genere:

 public event EventHandler SomethingHappened; void SomeMethod() { this.SomethingHappened.RaiseEvent(this, EventArgs.Empty); } 

La mia domanda: è una buona idea? Ci manca qualcosa non avendo il metodo standard On? (Una cosa che noto è che non funziona con eventi che hanno un codice di aggiunta / rimozione esplicito.)

Funzionerà ancora con eventi che hanno un add / remove esplicito: devi solo usare la variabile delegata (o comunque hai memorizzato il delegato) invece del nome dell’evento.

Tuttavia, c’è un modo più semplice per renderlo thread-safe – inizializzarlo con un gestore no-op:

 public event EventHandler SomethingHappened = delegate {}; 

Il risultato in termini di prestazioni nel chiamare un delegato in più sarà trascurabile e sicuramente faciliterà il codice.

A proposito, nel tuo metodo di estensione non hai bisogno di una variabile locale extra – puoi semplicemente fare:

 static public void RaiseEvent(this EventHandler @event, object sender, EventArgs e) { if (@event != null) @event(sender, e); } static public void RaiseEvent(this EventHandler @event, object sender, T e) where T : EventArgs { if (@event != null) @event(sender, e); } 

Personalmente non userò una parola chiave come nome parametro, ma in realtà non cambia affatto il lato chiamante, quindi fai ciò che vuoi 🙂

EDIT: Come per il metodo “OnXXX”: stai pensando di derivare dalle tue classi? A mio avviso, la maggior parte delle classi dovrebbe essere sigillata. Se lo fai , vuoi che quelle classi derivate siano in grado di aumentare l’evento? Se la risposta a una di queste domande è “no”, non preoccuparti. Se la risposta ad entrambi è “sì”, allora fallo 🙂

Ora C # 6 è qui, c’è un modo più compatto e sicuro per triggersre un evento:

 SomethingHappened?.Invoke(this, e); 

Invoke() viene chiamato solo se i delegati sono registrati per l’evento (cioè non è null), grazie all’operatore null-condizionale, “?”.

Il problema di threading il codice “gestore” nella domanda che si propone di risolvere viene spostato qui perché, come in quel codice, si accede a SomethingHappened solo una volta, quindi non c’è possibilità che venga impostato su null tra test e invocazione.

Questa risposta è forse tangenziale alla domanda originale, ma molto pertinente per coloro che cercano un metodo più semplice per aumentare gli eventi.

[Ecco un pensiero]

Basta scrivere il codice una volta nel modo consigliato e farlo con esso. Quindi non confonderai i tuoi colleghi guardando oltre il codice pensando di aver fatto qualcosa di sbagliato?

[Ho letto altri post cercando di trovare modi per scrivere un gestore di eventi di quanto non abbia mai passato a scrivere un gestore di eventi.]

Meno codice, più leggibile. Me piace.

Se non sei interessato al rendimento, puoi dichiarare il tuo evento come questo per evitare il controllo nullo:

 public event EventHandler SomethingHappened = delegate{}; 

Non stai “assicurando” la sicurezza del thread assegnando il gestore a una variabile locale. Il tuo metodo potrebbe ancora essere interrotto dopo l’assegnazione. Se, ad esempio, la class che ha ascoltato l’evento viene eliminata durante l’interruzione, si chiama un metodo in una class dismessa.

Stai salvando te stesso da un’eccezione di riferimento null, ma ci sono modi più semplici per farlo, come hanno sottolineato Jon Skeet e cristianlibardo nelle loro risposte.

Un’altra cosa è che per le classi non sigillate, il metodo OnFoo dovrebbe essere virtuale, cosa che non penso sia ansible con i metodi di estensione.