Firma dell'evento in .NET: si utilizza un "mittente" di tipo forte?


106

Mi rendo perfettamente conto che quello che sto proponendo non segue le linee guida .NET e, quindi, è probabilmente una cattiva idea solo per questo motivo. Tuttavia, vorrei considerare questo da due possibili prospettive:

(1) Devo considerare di utilizzarlo per il mio lavoro di sviluppo, che è al 100% per scopi interni.

(2) È questo un concetto che i progettisti del framework potrebbero considerare di modificare o aggiornare?

Sto pensando di utilizzare una firma di evento che utilizzi un "mittente" fortemente digitato, invece di digitarlo come "oggetto", che è l'attuale modello di progettazione .NET. Cioè, invece di utilizzare una firma di evento standard simile a questa:

class Publisher
{
    public event EventHandler<PublisherEventArgs> SomeEvent;
}

Sto valutando di utilizzare una firma di evento che utilizza un parametro "mittente" di tipo forte, come segue:

Per prima cosa, definisci un "StrongTypedEventHandler":

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

Questo non è poi così diverso da un Action <TSender, TEventArgs>, ma facendo uso di StrongTypedEventHandler, applichiamo che TEventArgs deriva da System.EventArgs.

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

class Publisher
{
    public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;

    protected void OnSomeEvent()
    {
        if (SomeEvent != null)
        {
            SomeEvent(this, new PublisherEventArgs(...));
        }
    }
}

La disposizione di cui sopra consentirebbe agli abbonati di utilizzare un gestore di eventi di tipo forte che non richiede il casting:

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

Mi rendo perfettamente conto che ciò rompe con il modello standard di gestione degli eventi .NET; tuttavia, tieni presente che la controvarianza consentirebbe a un abbonato di utilizzare una firma di gestione degli eventi tradizionale, se lo desidera:

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

Cioè, se un gestore di eventi ha bisogno di sottoscrivere eventi da tipi di oggetti disparati (o forse sconosciuti), il gestore potrebbe digitare il parametro "mittente" come "oggetto" per gestire l'intera ampiezza dei potenziali oggetti mittente.

Oltre a infrangere le convenzioni (che è qualcosa che non prendo alla leggera, credimi) non riesco a pensare ad alcun aspetto negativo di questo.

Potrebbero esserci alcuni problemi di conformità con CLS qui. Questo funziona in Visual Basic .NET 2008 al 100% (l'ho testato), ma credo che le versioni precedenti di Visual Basic .NET fino al 2005 non abbiano covarianza e controvarianza delegata. [Modifica: da allora l'ho provato, ed è confermato: VB.NET 2005 e versioni precedenti non possono gestirlo, ma VB.NET 2008 va bene al 100%. Vedi "Modifica # 2", di seguito.] Potrebbero esserci altri linguaggi .NET che hanno anche un problema con questo, non posso esserne sicuro.

Ma non mi vedo sviluppare per alcun linguaggio diverso da C # o Visual Basic .NET, e non mi dispiace limitarlo a C # e VB.NET per .NET Framework 3.0 e versioni successive. (Non potevo immaginare di tornare alla 2.0 a questo punto, ad essere onesti.)

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

Ecco alcuni collegamenti correlati che ho trovato:

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

(2) Generazione di eventi semplice in C #: utilizzo di "mittente" rispetto a EventArgs personalizzato [StackOverflow 2009]

(3) Pattern di firma dell'evento in .net [StackOverflow 2008]

Sono interessato all'opinione di chiunque e di tutti su questo ...

Grazie in anticipo,

Mike

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

Ecco un esempio funzionante completo che mostra che sia i gestori di eventi di tipo forte che gli attuali gestori di eventi standard che utilizzano un parametro "mittente oggetto" possono coesistere con questo approccio. Puoi copiare e incollare il codice e provarlo:

namespace csScrap.GenericEventHandling
{
    class PublisherEventArgs : EventArgs
    {
        // ...
    }

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

    class Publisher
    {
        public event StrongTypedEventHandler<Publisher, PublisherEventArgs> 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();
        }
    }
}

Modifica n. 2: questo è in risposta alla dichiarazione di Andrew Hare riguardo a covarianza e controvarianza e come si applica qui. I delegati nel linguaggio C # hanno avuto covarianza e controvarianza per così tanto tempo che sembra semplicemente "intrinseco", ma non lo è. Potrebbe anche essere qualcosa che è abilitato in 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 precedenti non sarebbero 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

    <SerializableAttribute()> _
    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 l'ho testato su VB.NET 2005, giusto per essere sicuro, e non si compila, affermando:

Il metodo 'Public Sub SomeEventHandler (sender As Object, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' non ha la stessa firma del delegato 'Delegate Sub StrongTypedEventHandler (Of TSender, TEventArgs As System.EventArgs) (sender As Publisher, egs) '

Fondamentalmente, i delegati sono invarianti nelle versioni VB.NET 2005 e precedenti. In realtà ho pensato a questa idea un paio di anni fa, ma l'incapacità di VB.NET di affrontarlo mi ha infastidito ... Ma ora sono passato saldamente a C # e VB.NET ora può gestirlo, quindi, beh, da qui questo post.

Modifica: aggiorna n. 3

Ok, lo sto usando con successo da un po 'di tempo. È davvero un bel sistema. Ho deciso di chiamare il mio "StrongTypedEventHandler" "GenericEventHandler", definito come segue:

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

Oltre a questa ridenominazione, 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 e i dati dell'evento. Le firme del gestore eventi devono seguire questo formato: void MyEventHandler (object sender, EventArgs e). Il parametro 'sender' è sempre di tipo System.Object, anche se è possibile utilizzare un tipo più specifico. Il parametro "e" è sempre di tipo System.EventArgs. Gli eventi che non forniscono dati sugli eventi dovrebbero utilizzare il tipo di 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 obiettivo andrebbe perso dopo la prima chiamata. "

Ovviamente sappiamo tutto questo e stiamo comunque infrangendo le regole. (Tutti i gestori di eventi possono utilizzare lo standard "Mittente oggetto" nella loro firma, se lo si preferisce in ogni caso - questa è una modifica senza interruzioni.)

Quindi l'uso di a SuppressMessageAttributefa il trucco:

[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
    Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> 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


6
Fallo. (Non credo che questo giustifichi una risposta.)
Konrad Rudolph,

1
Le mie argomentazioni non erano realmente rivolte a te: ovviamente dovresti farlo nei tuoi progetti. Erano argomenti sul motivo per cui potrebbe non funzionare nel BCL.
Tommy Carlier,

3
Amico, vorrei che il mio progetto lo avesse fatto dall'inizio, odio lanciare il mittente.
Matt H

7
Ora QUESTA è una domanda. Vedi, gente? Non una di queste oh hi this my hom work solve it plz :code dump:domande di dimensioni tweet , ma una domanda dalla quale impariamo .
Camilo Martin

3
Un altro suggerimento, basta il nome è EventHandler<,>quello GenericEventHandler<,>. Esiste già un generico EventHandler<>in BCL denominato semplicemente EventHandler. Quindi EventHandler è un nome più comune e i delegati supportano i sovraccarichi di tipo
nawfal

Risposte:


25

Sembra che Microsoft abbia colto questo dato che un esempio simile è ora su MSDN:

Delegati generici


2
+1 Ah, eccellente. Hanno capito questo davvero. Questo è buono. Spero, tuttavia, che lo rendano un modello riconosciuto all'interno di VS IDE, perché, come è ora, è più scomodo utilizzare questo modello in termini di IntelliSense, ecc.
Mike Rosenblum

13

Quello che stai proponendo ha molto senso in realtà, e mi chiedo solo se questa sia una di quelle cose che è semplicemente così perché è stato originariamente progettato prima dei generici, o se c'è una vera ragione per questo.


1
Sono sicuro che questo sia esattamente il motivo. Tuttavia, ora che le versioni più recenti del linguaggio hanno controvarianza per gestire questo problema, sembra che dovrebbero essere in grado di gestirlo in modo retrocompatibile. I gestori precedenti che utilizzano un "oggetto mittente" non si interrompevano. Ma questo non è vero per i linguaggi più vecchi e potrebbe non essere vero per alcuni linguaggi .NET correnti, non ne sono sicuro.
Mike Rosenblum

13

Windows Runtime (WinRT) introduce un TypedEventHandler<TSender, TResult>delegato, che fa esattamente quello che fa il tuo StrongTypedEventHandler<TSender, TResult>, ma apparentemente senza il vincolo sul TResultparametro type:

public delegate void TypedEventHandler<TSender, TResult>(TSender sender,
                                                         TResult args);

La documentazione MSDN è qui .


1
Ah, è bello vedere che ci sono progressi ... Mi chiedo perché TResult non si limiti ad ereditare dalla classe 'EventArgs'. La classe di base "EventArgs" è sostanzialmente vuota; forse si stanno allontanando da questa restrizione?
Mike Rosenblum

Potrebbe essere una svista da parte del team di progettazione; chissà.
Pierre Arnaud

beh, gli eventi funzionano bene senza l'utilizzo EventArgs, è solo una cosa da convenzione
Sebastian

3
Indica specificamente nella documentazione di TypedEventHandler che argssarà nullse non ci sono dati di evento, quindi sembra che stiano allontanandosi dall'usare un oggetto essenzialmente vuoto per impostazione predefinita. Immagino che l'idea originale fosse che un metodo con un secondo parametro di tipo EventArgspotesse gestire qualsiasi evento perché i tipi sarebbero sempre compatibili. Probabilmente si stanno rendendo conto ora che essere in grado di gestire più eventi diversi con un metodo non è poi così importante.
jmcilhinney

1
Non sembra una svista. Il vincolo è stato rimosso anche dal delegato System.EventHandler <TEventArgs>. Referencesource.microsoft.com/#mscorlib/system/…
colton7909

5

Sono in discussione con le seguenti dichiarazioni:

  • Credo che le versioni precedenti di Visual Basic .NET fino al 2005 non abbiano covarianza e controvarianza delegata.
  • Mi rendo perfettamente conto che questo rasenta la blasfemia.

Prima di tutto, nulla di ciò che hai fatto qui ha a che fare con la covarianza o la controvarianza. ( Modifica: l'affermazione precedente è sbagliata, per ulteriori informazioni vedere Covarianza e controvarianza nei delegati ) Questa soluzione funzionerà perfettamente in tutte le versioni di CLR 2.0 e successive (ovviamente questo non funzionerà in un'applicazione CLR 1.0 poiché utilizza generici).

In secondo luogo, non sono assolutamente d'accordo sul fatto che la tua idea rasenta la "blasfemia" in quanto è un'idea meravigliosa.


2
Ciao Andrew, grazie per il pollice in alto! Considerando il tuo livello di reputazione, questo significa davvero molto per me ... Sulla questione della covarianza / controvarianza: se il delegato fornito dall'abbonato non corrisponde esattamente alla firma dell'evento dell'editore, allora sono coinvolte covarianza e controvarianza. C # ha avuto covarianza e controvarianza delegata da sempre, quindi sembra intrinseca, ma VB.NET non aveva covarianza e controvarianza delegata fino a .NET 3.0. Pertanto, VB.NET per .NET 2.0 e versioni precedenti non sarebbe in grado di utilizzare questo sistema. (Vedi l'esempio di codice che ho aggiunto in "Modifica n. 2" sopra.)
Mike Rosenblum,

@ Mike - Le mie scuse, hai ragione al 100%! Ho modificato la mia risposta per riflettere il tuo punto :)
Andrew Hare

4
Ah, interessante! Sembra che la covarianza / controvarianza delegata faccia parte del CLR, ma (per ragioni che non so) non è stata esposta da VB.NET prima della versione più recente. Ecco un articolo di Francesco Balena che mostra come si può ottenere la varianza delegata utilizzando Reflection, se non abilitato dal linguaggio stesso: dotnet2themax.com/blogs/fbalena/… .
Mike Rosenblum

1
@Mike - È sempre interessante apprendere le cose che il CLR supporta ma non sono supportate in nessuno dei linguaggi .NET.
Andrew Hare

5

Ho dato un'occhiata a come è stato gestito con il nuovo WinRT e sulla base di altre opinioni qui, e alla fine ho deciso di farlo in questo modo:

[Serializable]
public delegate void TypedEventHandler<in TSender, in TEventArgs>(
    TSender sender,
    TEventArgs e
) where TEventArgs : EventArgs;

Questa sembra essere la soluzione migliore considerando l'uso del nome TypedEventHandler in WinRT.


Perché aggiungere la restrizione generica su TEventArgs? È stato rimosso da EventHandler <> e TypedEventHandler <,> perché non aveva davvero senso.
Mike Marynowski

2

Penso che sia una grande idea e MS potrebbe semplicemente non avere il tempo o l'interesse da investire per renderlo migliore, come ad esempio quando sono passati da ArrayList a elenchi basati su generici.


Potresti avere ragione ... D'altra parte, penso che sia solo uno "standard" e forse non è affatto un problema tecnico. Cioè, questa capacità potrebbe essere presente in tutti i linguaggi .NET correnti, non lo so. So che C # e VB.NET possono gestirlo. Tuttavia, non sono sicuro di quanto questo funzioni in generale in tutti gli attuali linguaggi .NET ... Ma poiché funziona in C # e VB.NET, e tutti qui sono così di supporto, penso che molto probabilmente lo farò. :-)
Mike Rosenblum

2

Da quanto ho capito, il campo "Mittente" dovrebbe sempre fare riferimento all'oggetto che contiene l'abbonamento all'evento. Se avessi i miei druther, ci sarebbe anche un campo contenente informazioni sufficienti per annullare l'iscrizione a un evento se fosse necessario (*) (si consideri, ad esempio, un registratore di modifiche che si iscrive a eventi di "raccolta modificata"; contiene due parti , uno dei quali fa il lavoro effettivo e contiene i dati effettivi, e l'altro fornisce un wrapper dell'interfaccia pubblica, la parte principale potrebbe contenere un riferimento debole alla parte wrapper. Se la parte wrapper viene raccolta dalla spazzatura, ciò significherebbe non c'era più nessuno interessato ai dati che venivano raccolti e il change-logger dovrebbe quindi cancellarsi da ogni evento che riceve).

Poiché è possibile che un oggetto possa inviare eventi per conto di un altro oggetto, posso vedere una potenziale utilità per avere un campo "mittente" che è di tipo Oggetto e per avere il campo derivato da EventArgs contenere un riferimento all'oggetto che dovrebbe essere agito. L'utilità del campo "mittente", tuttavia, è probabilmente limitata dal fatto che non esiste un modo pulito per un oggetto di annullare l'iscrizione a un mittente sconosciuto.

(*) In realtà, un modo più pulito di gestire le cancellazioni sarebbe quello di avere un tipo di delegato multicast per le funzioni che restituiscono Boolean; se una funzione chiamata da un tale delegato restituisce True, al delegato verrà applicata la patch per rimuovere quell'oggetto. Ciò significherebbe che i delegati non sarebbero più veramente immutabili, ma dovrebbe essere possibile effettuare tale modifica in modo thread-safe (ad esempio, annullando il riferimento all'oggetto e facendo in modo che il codice del delegato multicast ignori qualsiasi riferimento a oggetti nulli incorporati). In questo scenario, un tentativo di pubblicazione e di un evento su un oggetto eliminato potrebbe essere gestito in modo molto pulito, indipendentemente dalla provenienza dell'evento.


2

Guardando indietro alla blasfemia come unica ragione per rendere il mittente un tipo di oggetto (se si omettono problemi di controvarianza nel codice VB 2005, che è un errore di Microsoft IMHO), qualcuno può suggerire almeno un motivo teorico per inchiodare il secondo argomento al tipo EventArgs. Andando ancora oltre, c'è una buona ragione per conformarsi alle linee guida e alle convenzioni di Microsoft in questo caso particolare?

Avere bisogno di sviluppare un altro wrapper EventArgs per altri dati che vogliamo passare all'interno del gestore di eventi sembra strano, perché non è possibile passare direttamente quei dati lì. Considera le seguenti sezioni di codice

[Esempio 1]

public delegate void ConnectionEventHandler(Server sender, Connection connection);

public partial class Server
{
    protected virtual void OnClientConnected(Connection connection)
    {
        if (ClientConnected != null) ClientConnected(this, connection);
    }

    public event ConnectionEventHandler ClientConnected;
}

[Esempio 2]

public delegate void ConnectionEventHandler(object sender, ConnectionEventArgs e);

public class ConnectionEventArgs : EventArgs
{
    public Connection Connection { get; private set; }

    public ConnectionEventArgs(Connection connection)
    {
        this.Connection = connection;
    }
}

public partial class Server
{
    protected virtual void OnClientConnected(Connection connection)
    {
        if (ClientConnected != null) ClientConnected(this, new ConnectionEventArgs(connection));
    }

    public event ConnectionEventHandler ClientConnected;
}

2
Sì, la creazione di una classe separata che eredita da System.EventArgs può sembrare poco intuitiva e rappresenta un lavoro extra, ma c'è un'ottima ragione per questo. Se non hai mai bisogno di cambiare il tuo codice, allora il tuo approccio va bene. Ma la realtà è che potresti dover aumentare la funzionalità dell'evento in una versione futura e aggiungere proprietà agli argomenti dell'evento. Nel tuo scenario, dovresti aggiungere sovraccarichi extra o parametri opzionali alla firma del gestore eventi. Questo è un approccio utilizzato in VBA e VB 6.0 legacy, che è praticabile, ma un po 'brutto nella pratica.
Mike Rosenblum

1
Ereditando da EventArgs, tuttavia, una versione futura potrebbe ereditare dalla classe degli argomenti dell'evento precedente e aumentarla. Tutti i chiamanti più vecchi potrebbero ancora funzionare esattamente come sono, operando contro la classe base della nuova classe di argomenti dell'evento. Molto pulito. Più lavoro per te, ma più pulito per tutti i chiamanti che dipendono dalla tua libreria.
Mike Rosenblum

Non ha nemmeno bisogno di ereditarlo, puoi semplicemente aggiungere la funzionalità aggiuntiva direttamente nella tua classe degli argomenti dell'evento e continuerà a funzionare bene. Detto questo, la restrizione che fissa gli argomenti a eventargs è stata rimossa perché non aveva molto senso per molti scenari, ad es. quando sai che non avrai mai bisogno di espandere la funzionalità di un particolare evento o quando tutto ciò di cui hai bisogno è un tipo di valore arg in applicazioni molto sensibili alle prestazioni.
Mike Marynowski

1

Con la situazione corrente (mittente è oggetto), puoi facilmente allegare un metodo a più eventi:

button.Click += ClickHandler;
label.Click += ClickHandler;

void ClickHandler(object sender, EventArgs e) { ... }

Se il mittente fosse generico, il target dell'evento click non sarebbe di tipo Button o Label, ma di tipo Control (perché l'evento è definito su Control). Quindi alcuni eventi sulla classe Button avrebbero un target di tipo Control, altri avrebbero altri tipi di target.


2
Tommy, puoi fare esattamente la stessa cosa con il sistema che ti propongo. È comunque possibile utilizzare un gestore di eventi standard che dispone di un parametro "mittente oggetto" per gestire questi eventi di tipo forte. (Vedi l'esempio di codice che ho ora aggiunto al post originale.)
Mike Rosenblum

Sì, sono d'accordo, questo è l'aspetto positivo degli eventi .NET standard, accettato!
Lu4

1

Non credo ci sia niente di sbagliato in quello che vuoi fare. Per la maggior parte, sospetto che il object senderparametro rimanga per continuare a supportare il codice pre 2.0.

Se vuoi davvero apportare questa modifica per un'API pubblica, potresti prendere in considerazione la creazione della tua classe EvenArgs di base. Qualcosa come questo:

public class DataEventArgs<TSender, TData> : EventArgs
{
    private readonly TSender sender, TData data;

    public DataEventArgs(TSender sender, TData data)
    {
        this.sender = sender;
        this.data = data;
    }

    public TSender Sender { get { return sender; } }
    public TData Data { get { return data; } }
}

Quindi puoi dichiarare i tuoi eventi in questo modo

public event EventHandler<DataEventArgs<MyClass, int>> SomeIndexSelected;

E metodi come questo:

private void HandleSomething(object sender, EventArgs e)

sarà ancora in grado di iscriversi.

MODIFICARE

Quest'ultima riga mi ha fatto riflettere un po '... Dovresti effettivamente essere in grado di implementare ciò che proponi senza interrompere alcuna funzionalità esterna poiché il runtime non ha problemi a ridurre i parametri. Sarei comunque propenso alla DataEventArgssoluzione (personalmente). Lo farei, sapendo però che è ridondante, visto che il mittente è memorizzato nel primo parametro e come proprietà dell'evento args.

Un vantaggio di attenersi a DataEventArgsè che è possibile concatenare gli eventi, cambiando il mittente (per rappresentare l'ultimo mittente) mentre EventArgs mantiene il mittente originale.


Ehi Michael, questa è un'alternativa piuttosto carina. Mi piace. Come hai detto, però, è ridondante che il parametro "mittente" sia effettivamente passato due volte. Un approccio simile è discusso qui: stackoverflow.com/questions/809609/… , e il consenso sembra essere che sia troppo non standard. Questo è il motivo per cui ero riluttante a suggerire l'idea di "mittente" con caratteri forti qui. (Sembra essere ben accolto, quindi sono contento.)
Mike Rosenblum,

1

Fallo. Per il codice non basato su componenti, spesso semplifico le firme degli eventi in modo semplice

public event Action<MyEventType> EventName

da dove MyEventTypenon eredita EventArgs. Perché preoccuparsi, se non ho mai intenzione di utilizzare nessuno dei membri di EventArgs.


1
Essere d'accordo! Perché dovremmo sentirci scimmie?
Lu4

1
+ 1-ed, questo è quello che uso anch'io. A volte la semplicità vince! O anche event Action<S, T>, event Action<R, S, T>ecc. Ho un metodo di estensione per Raiseloro thread-
safe
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.