2009-07-13 5 views
20

C'è un modello standard per gli eventi in .NET: utilizzano un tipo delegate che accetta un oggetto semplice chiamato mittente e quindi il "payload" effettivo in un secondo parametro, che deve essere derivato da EventArgs.Cosa dovrei perdere abbandonando il modello standard EventHandler in .NET?

Il motivo per cui il secondo parametro è derivato da EventArgs sembra piuttosto chiaro (vedere .NET Framework Standard Library Annotated Reference). Ha lo scopo di garantire la compatibilità binaria tra sink di eventi e sorgenti man mano che il software evolve. Per ogni evento, anche se ha solo un argomento, deriviamo una classe di argomenti evento personalizzata che ha una singola proprietà contenente quell'argomento, in questo modo conserviamo la possibilità di aggiungere più proprietà al carico utile nelle versioni future senza rompere il codice client esistente . Molto importante in un ecosistema di componenti sviluppati in modo indipendente.

Ma trovo che lo stesso vale per argomenti zero. Questo vuol dire che se ho un evento che non ha argomenti nella mia prima versione, e scrivo:

public event EventHandler Click; 

... allora sto facendo male. Se cambio il tipo delegato in futuro di una nuova classe come il suo carico utile:

public class ClickEventArgs : EventArgs { ... 

... spezzerò la compatibilità binaria con i miei clienti. Il client termina associato a un sovraccarico specifico di un metodo interno add_Click che impiega EventHandler e se si modifica il tipo di delegato, non è possibile trovare tale sovraccarico, quindi è disponibile un MissingMethodException.

OK, quindi cosa succede se uso la versione generica a portata di mano?

public EventHandler<EventArgs> Click; 

No, ancora sbagliata, perché un EventHandler<ClickEventArgs> non è un EventHandler<EventArgs>.

Quindi, per ottenere il vantaggio di EventArgs, è possibile ottenere anziché utilizzarlo direttamente così com'è. Se non lo fai, potresti anche non usarlo (mi sembra).

Quindi c'è il primo argomento, sender. Mi sembra una ricetta per l'accoppiamento empio. L'attivazione di un evento è essenzialmente una chiamata di funzione. Se la funzione, in generale, ha la capacità di tornare indietro nello stack e scoprire chi era il chiamante, e regolare il suo comportamento di conseguenza? Dovremmo mandare le interfacce a questo aspetto?

public interface IFoo 
{ 
    void Bar(object caller, int actualArg1, ...); 
} 

Dopo tutto, l'implementor di Bar potrebbe desiderare di sapere chi è il caller era, in modo che possano eseguire una query per ulteriori informazioni! Spero che tu ora stia vomitando. Perché dovrebbe essere diverso per gli eventi?

Quindi, anche se sono pronto a prendere il dolore di fare un autonomo EventArgs classe -derived per ogni evento Dichiaro, proprio per rendere la pena mia mentre usando EventArgs a tutti, ho sicuramente preferirei far cadere l'argomento object sender .

visiva funzione di autocompletamento di Studio non sembra cura ciò che delegato si utilizza per un evento - è possibile digitare +=[premete Spazio, Return] e scrive un metodo di gestore per te, che le partite qualunque delegato Capita di essere .

Quindi quale valore dovrei perdere deviando dal modello standard?

Come domanda bonus, C#/CLR 4.0 farà qualcosa per cambiare questo, forse attraverso la contravarianza nei delegati? Ho provato a indagare su questo, ma ho colpito another problem. In origine avevo incluso questo aspetto della domanda in quell'altra domanda, ma lì causava confusione. E sembra un po 'troppo per dividere questo in su in un totale di tre domande ...

Aggiornamento:

Risulta ho fatto bene a interrogarsi sugli effetti del controvarianza su tutta questa faccenda!

Come noted elsewhere, le nuove regole del compilatore lasciano un buco nel sistema di tipi che esplode in fase di esecuzione. Il foro è stato effettivamente collegato definendo EventHandler<T> in modo diverso a Action<T>.

Quindi per gli eventi, per evitare quel tipo di foro non è necessario utilizzare Action<T>. Ciò non significa che devi usare EventHandler<TEventArgs>; significa semplicemente che se si utilizza un tipo di delegato generico, non selezionarne uno abilitato per controvarianza.

+0

Essere interessati a sentire un motivo da downvoter. –

+0

Non ho votato in entrambi i casi, ma sospetto che il downvote sia perché hai almeno due domande in una qui. Non voglio davvero provare ad affrontare il tutto da solo, ma sarei felice di guardare l'aspetto della varianza (come sto scrivendo in questo momento). Se potessi separare il bit che porta a un errore di esecuzione in una domanda separata con un caso breve ma completo per riprodurlo, sarei interessato ... –

+0

Farà [IMBOTTITURA] –

risposta

7

Nulla, non si perde nulla. Sto usando Action<> da quando .NET 3.5 è uscito ed è molto più naturale e più facile da programmare contro.

io non anche trattare con il tipo EventHandler per i gestori di eventi generati più, è sufficiente scrivere la firma del metodo che si desidera e il filo in su con un lambda:

btnCompleteOrder.OnClick += (o,e) => _presenter.CompleteOrder(); 
+2

Non hai problemi di annullamento dell'iscrizione all'evento con questa funzione anonima? – Matt

+2

Suppongo che lo faresti, ma non dovresti davvero annullare la sottoscrizione ad un evento OnClick, poiché questo è semplicemente inteso a notificare al tuo presentatore che è successo qualcosa.Se stai sottoscrivendo/annullando l'iscrizione probabilmente ti interesserai di più agli eventi generati da classi non-ui, in tal caso hai il pieno controllo del tipo di evento e puoi comunque renderlo un'azione <> in modo che le funzioni anonime non siano necessarie. –

+0

@ Matt - Scrivo la maggior parte del mio evento arruolando in quel modo. La necessità di cancellarsi esplicitamente è abbastanza insolita. Essenzialmente se l'origine dell'evento è un oggetto più longevo (ad esempio memorizzato in una statica o nella finestra principale dell'applicazione) ma se l'origine e il sink hanno la stessa durata, perché preoccuparsi? Questo è ciò che il GC è per. –

1

Non mi piace l'event- modello di gestore o. A mio parere, l'oggetto Sender non è davvero così utile. Nei casi in cui un evento sta dicendo che qualcosa è accaduto ad un oggetto (ad esempio una notifica di modifica), sarebbe più utile avere le informazioni in EventArgs. L'unico utilizzo che potrei fare, tipo vedere per Sender, sarebbe annullare l'iscrizione a un evento, ma non è sempre chiaro quale evento si dovrebbe annullare.

Per inciso, se avessi i miei druthers, un evento non sarebbe un metodo AddHandler e un metodo RemoveHandler; sarebbe solo un metodo AddHandler che restituirebbe un MethodInvoker che potrebbe essere utilizzato per la disiscrizione. Piuttosto che un argomento mittente, avrei il primo argomento essere una copia del MethodInvoker richiesto per la disiscrizione (nel caso in cui un oggetto si trovi a ricevere eventi a cui è stato perso l'invocatore di annullamento dell'iscrizione). Il MulticastDelegate standard non sarebbe adatto per gli eventi di distribuzione (poiché ogni abbonato dovrebbe ricevere un delegato di annullamento della sottoscrizione diverso) ma gli eventi di annullamento dell'iscrizione non richiederebbero una ricerca lineare attraverso un elenco di chiamate.