Firma evento in .NET – Utilizzo di un “mittente” fortemente digitato?

Mi rendo perfettamente conto che ciò che sto proponendo non segue le linee guida .NET e, pertanto, è probabilmente una ctriggers idea solo per questa ragione. Tuttavia, vorrei considerare questo da due possibili prospettive:

(1) Dovrei considerare l’utilizzo di questo per il mio lavoro di sviluppo, che è al 100% per scopi interni.

(2) Si tratta di un concetto che i progettisti del framework potrebbero considerare di cambiare o aggiornare?

Sto pensando di utilizzare una firma di evento che utilizza un “mittente” fortemente digitato, invece di digitarlo come “object”, che è il modello di progettazione .NET corrente. Cioè, invece di usare una firma di evento standard che assomiglia a questo:

class Publisher { public event EventHandler SomeEvent; } 

Sto prendendo in considerazione l’utilizzo di una firma di evento che utilizza un parametro ‘sender’ fortemente tipizzato, come segue:

Innanzitutto, definisci un “StrongTypedEventHandler”:

 [SerializableAttribute] public delegate void StrongTypedEventHandler( TSender sender, TEventArgs e ) where TEventArgs : EventArgs; 

Questo non è molto diverso da un’azione , ma facendo uso di StrongTypedEventHandler , StrongTypedEventHandler che TEventArgs deriva da System.EventArgs .

Successivamente, ad esempio, possiamo utilizzare StrongTypedEventHandler in una class di pubblicazione come segue:

 class Publisher { public event StrongTypedEventHandler SomeEvent; protected void OnSomeEvent() { if (SomeEvent != null) { SomeEvent(this, new PublisherEventArgs(...)); } } } 

La suddetta disposizione consentirebbe agli abbonati di utilizzare un gestore di eventi fortemente tipizzato che non richiedesse il casting:

 class Subscriber { void SomeEventHandler(Publisher sender, PublisherEventArgs e) { if (sender.Name == "John Smith") { // ... } } } 

Mi rendo perfettamente conto che ciò si interrompe con lo schema di gestione degli eventi standard di .NET; tuttavia, tieni presente che la controvarianza consentirebbe a un sottoscrittore di utilizzare una firma di gestione eventi tradizionale, se lo desideri:

 class Subscriber { void SomeEventHandler(object sender, PublisherEventArgs e) { if (((Publisher)sender).Name == "John Smith") { // ... } } } 

Cioè, se un gestore di eventi aveva bisogno di sottoscrivere eventi da tipi di oggetti disparati (o forse sconosciuti), il gestore poteva digitare il parametro ‘mittente’ come ‘object’ per gestire l’intera larghezza di potenziali oggetti mittente.

Oltre a rompere le convenzioni (che è qualcosa che non prendo alla leggera, credetemi) non posso pensare ad alcun aspetto negativo di questo.

Potrebbero esserci alcuni problemi di conformità CLS qui. Questo funziona in Visual Basic .NET 2008 al 100% (ho provato), ma credo che le versioni precedenti di Visual Basic .NET fino al 2005 non abbiano delegato covarianza e controvarianza. [Modifica: l’ho testato e confermato: VB.NET 2005 e versioni successive non sono in grado di gestirli, ma VB.NET 2008 è corretto al 100%. Vedi “Modifica n. 2”, di seguito.] Ci possono essere altri linguaggi .NET che hanno anche un problema con questo, non posso esserne sicuro.

Ma non mi vedo sviluppando per una lingua diversa da C # o Visual Basic .NET e non mi interessa limitarla a C # e VB.NET per .NET Framework 3.0 e versioni successive. (Non potrei immaginare di tornare a 2.0 a questo punto, per essere onesti.)

Qualcun altro può pensare a un problema con questo? O semplicemente rompe le convenzioni così tanto da far girare lo stomaco alle persone?

Ecco alcuni link correlati che ho trovato:

(1) Linee guida per la progettazione degli eventi [MSDN 3.5]

(2) C # Event Raising semplice – utilizzo di “mittente” rispetto a EventArgs personalizzato [StackOverflow 2009]

(3) Schema di firma degli eventi in .net [StackOverflow 2008]

Sono interessato all’opinione di chiunque e di tutti su questo …

Grazie in anticipo,

Mike

Modifica n. 1: è in risposta al post di Tommy Carlier :

Ecco un esempio funzionante completo che mostra che sia i gestori di eventi con caratteri forti che i gestori di eventi standard correnti che utilizzano un parametro ‘mittente dell’object’ possono coesistere con questo approccio. Puoi copiare e incollare nel codice e dargli una corsa:

 namespace csScrap.GenericEventHandling { class PublisherEventArgs : EventArgs { // ... } [SerializableAttribute] public delegate void StrongTypedEventHandler( TSender sender, TEventArgs e ) where TEventArgs : EventArgs; class Publisher { public event StrongTypedEventHandler SomeEvent; public void OnSomeEvent() { if (SomeEvent != null) { SomeEvent(this, new PublisherEventArgs()); } } } class StrongTypedSubscriber { public void SomeEventHandler(Publisher sender, PublisherEventArgs e) { MessageBox.Show("StrongTypedSubscriber.SomeEventHandler called."); } } class TraditionalSubscriber { public void SomeEventHandler(object sender, PublisherEventArgs e) { MessageBox.Show("TraditionalSubscriber.SomeEventHandler called."); } } class Tester { public static void Main() { Publisher publisher = new Publisher(); StrongTypedSubscriber strongTypedSubscriber = new StrongTypedSubscriber(); TraditionalSubscriber traditionalSubscriber = new TraditionalSubscriber(); publisher.SomeEvent += strongTypedSubscriber.SomeEventHandler; publisher.SomeEvent += traditionalSubscriber.SomeEventHandler; publisher.OnSomeEvent(); } } } 

Edit # 2: Questo è in risposta alla dichiarazione di Andrew Hare riguardante la covarianza e la contravarianza e come si applica qui. I delegati nel linguaggio C # hanno avuto covarianza e contravarianza per così tanto tempo da sembrare semplicemente “intrinseco”, ma non lo è. Potrebbe anche essere qualcosa abilitato nel CLR, non lo so, ma Visual Basic .NET non ha ottenuto la capacità di covarianza e controvarianza per i suoi delegati fino a .NET Framework 3.0 (VB.NET 2008). Di conseguenza, Visual Basic.NET per .NET 2.0 e versioni successive non sarebbe in grado di utilizzare questo approccio.

Ad esempio, l’esempio sopra può essere tradotto in VB.NET come segue:

 Namespace GenericEventHandling Class PublisherEventArgs Inherits EventArgs ' ... ' ... End Class  _ Public Delegate Sub StrongTypedEventHandler(Of TSender, TEventArgs As EventArgs) _ (ByVal sender As TSender, ByVal e As TEventArgs) Class Publisher Public Event SomeEvent As StrongTypedEventHandler(Of Publisher, PublisherEventArgs) Public Sub OnSomeEvent() RaiseEvent SomeEvent(Me, New PublisherEventArgs) End Sub End Class Class StrongTypedSubscriber Public Sub SomeEventHandler(ByVal sender As Publisher, ByVal e As PublisherEventArgs) MessageBox.Show("StrongTypedSubscriber.SomeEventHandler called.") End Sub End Class Class TraditionalSubscriber Public Sub SomeEventHandler(ByVal sender As Object, ByVal e As PublisherEventArgs) MessageBox.Show("TraditionalSubscriber.SomeEventHandler called.") End Sub End Class Class Tester Public Shared Sub Main() Dim publisher As Publisher = New Publisher Dim strongTypedSubscriber As StrongTypedSubscriber = New StrongTypedSubscriber Dim traditionalSubscriber As TraditionalSubscriber = New TraditionalSubscriber AddHandler publisher.SomeEvent, AddressOf strongTypedSubscriber.SomeEventHandler AddHandler publisher.SomeEvent, AddressOf traditionalSubscriber.SomeEventHandler publisher.OnSomeEvent() End Sub End Class End Namespace 

VB.NET 2008 può eseguirlo al 100%. Ma ora lo ho testato su VB.NET 2005, per sicurezza, e non viene compilato, affermando:

Metodo ‘Public Sub SomeEventHandler (Sender As Object, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)’ non ha la stessa firma di delegate ‘Delegate Sub StrongTypedEventHandler (Of TSender, TEventArgs As System.EventArgs) (sender As Publisher, e As PublisherEventArgs) ‘

Fondamentalmente, i delegati sono invarianti nelle versioni VB.NET 2005 e seguenti. In realtà ho pensato a questa idea un paio di anni fa, ma l’incapacità di VB.NET di occuparmi di questo mi ha infastidito … Ma ora mi sono trasferito solidamente in C # e VB.NET ora può gestirlo, quindi, beh, quindi questo post.

Modifica: aggiornamento n. 3

Ok, ho usato questo con abbastanza successo per un po ‘di tempo. È davvero un bel sistema. Ho deciso di nominare il mio “StrongTypedEventHandler” come “GenericEventHandler”, definito come segue:

 [SerializableAttribute] public delegate void GenericEventHandler( TSender sender, TEventArgs e ) where TEventArgs : EventArgs; 

A parte questa rinomina, l’ho implementata esattamente come discusso sopra.

Fa inciampare sulla regola FxCop CA1009, che afferma:

“Per convenzione, gli eventi .NET hanno due parametri che specificano il mittente dell’evento ei dati dell’evento Le firme del gestore di eventi dovrebbero seguire questo modulo: void MyEventHandler (mittente dell’object, EventArgs e). Il parametro” sender “è sempre di tipo System.Object, anche se è ansible utilizzare un tipo più specifico.Il parametro ‘e’ è sempre di tipo System.EventArgs. Gli eventi che non forniscono dati di evento devono utilizzare il tipo delegato System.EventHandler.I gestori di eventi restituiscono void in modo che possano inviare ogni evento a più metodi di destinazione. Qualsiasi valore restituito da un objective andrebbe perso dopo la prima chiamata. ”

Certamente, noi sappiamo tutto questo e stiamo infrangendo le regole comunque. (Tutti i gestori di eventi possono utilizzare lo standard “Sender object” nella loro firma se si preferisce in ogni caso – questo è un cambiamento senza interruzione.)

Quindi l’uso di SuppressMessageAttribute fa il trucco:

 [SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly", Justification = "Using strong-typed GenericEventHandler event handler pattern.")] 

Spero che questo approccio diventi lo standard in futuro. Funziona davvero molto bene.

Grazie per tutte le vostre opinioni ragazzi, lo apprezzo davvero …

Mike