Ereignissignatur in .NET - Verwenden eines stark typisierten "Absenders"?

106

Mir ist völlig klar, dass das, was ich vorschlage, nicht den .NET-Richtlinien entspricht und daher wahrscheinlich allein aus diesem Grund eine schlechte Idee ist. Ich möchte dies jedoch aus zwei möglichen Perspektiven betrachten:

(1) Sollte ich in Betracht ziehen, dies für meine eigene Entwicklungsarbeit zu verwenden, die zu 100% für interne Zwecke bestimmt ist?

(2) Ist dies ein Konzept, das die Framework-Designer ändern oder aktualisieren könnten?

Ich denke darüber nach, eine Ereignissignatur zu verwenden, die einen stark typisierten "Absender" verwendet, anstatt ihn als "Objekt" zu tippen, was das aktuelle .NET-Entwurfsmuster ist. Das heißt, anstatt eine Standardereignissignatur zu verwenden, die folgendermaßen aussieht:

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

Ich erwäge die Verwendung einer Ereignissignatur, die einen stark typisierten Absenderparameter verwendet, wie folgt:

Definieren Sie zunächst einen "StrongTypedEventHandler":

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

Dies unterscheidet sich nicht allzu sehr von einer Aktion <TSender, TEventArgs>, aber durch die Verwendung von StrongTypedEventHandlererzwingen wir, dass die TEventArgs von abgeleitet sind System.EventArgs.

Als nächstes können wir als Beispiel den StrongTypedEventHandler in einer Publishing-Klasse wie folgt verwenden:

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

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

Die obige Anordnung würde es Abonnenten ermöglichen, einen stark typisierten Ereignishandler zu verwenden, für den kein Casting erforderlich ist:

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

Mir ist völlig klar, dass dies mit dem Standardmuster für die Behandlung von .NET-Ereignissen bricht. Beachten Sie jedoch, dass ein Abonnent durch Kontravarianz auf Wunsch eine herkömmliche Signatur für die Ereignisbehandlung verwenden kann:

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

Das heißt, wenn ein Ereignishandler Ereignisse von unterschiedlichen (oder möglicherweise unbekannten) Objekttypen abonnieren muss, kann der Handler den Parameter 'sender' als 'object' eingeben, um die gesamte Breite potenzieller Absenderobjekte zu verarbeiten.

Abgesehen davon, dass ich gegen die Konvention verstoße (was ich nicht leicht nehme, glauben Sie mir), kann ich mir keine Nachteile vorstellen.

Hier können einige CLS-Konformitätsprobleme auftreten. Dies läuft in Visual Basic .NET 2008 zu 100% in Ordnung (ich habe es getestet), aber ich glaube, dass die älteren Versionen von Visual Basic .NET bis 2005 keine delegierte Kovarianz und Kontravarianz aufweisen. [Bearbeiten: Ich habe dies seitdem getestet und es wurde bestätigt: VB.NET 2005 und niedriger können dies nicht verarbeiten, aber VB.NET 2008 ist zu 100% in Ordnung. Siehe "Bearbeiten # 2" weiter unten.] Möglicherweise gibt es andere .NET-Sprachen, bei denen ebenfalls ein Problem auftritt. Ich kann nicht sicher sein.

Ich sehe mich jedoch nicht für eine andere Sprache als C # oder Visual Basic .NET entwickelt, und es macht mir nichts aus, sie auf C # und VB.NET für .NET Framework 3.0 und höher zu beschränken. (Ich könnte mir nicht vorstellen, zu diesem Zeitpunkt auf 2.0 zurückzukehren, um ehrlich zu sein.)

Kann sich noch jemand ein Problem damit vorstellen? Oder bricht dies einfach so sehr mit der Konvention, dass sich die Mägen der Menschen drehen?

Hier sind einige verwandte Links, die ich gefunden habe:

(1) Richtlinien für das Event-Design [MSDN 3.5]

(2) C # simple Event Raising - Verwenden von "Absender" im Vergleich zu benutzerdefinierten EventArgs [StackOverflow 2009]

(3) Ereignissignaturmuster in .net [StackOverflow 2008]

Ich interessiere mich für jedermanns und jedermanns Meinung dazu ...

Danke im Voraus,

Mike

Edit # 1: Dies ist eine Antwort auf den Beitrag von Tommy Carlier :

Hier ist ein voll funktionsfähiges Beispiel, das zeigt, dass sowohl stark typisierte Ereignishandler als auch die aktuellen Standardereignishandler, die einen Parameter "Objektabsender" verwenden, mit diesem Ansatz koexistieren können. Sie können den Code kopieren, einfügen und ausführen:

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();
        }
    }
}

Edit # 2: Dies ist eine Antwort auf Andrew Hares Aussage bezüglich Kovarianz und Kontravarianz und wie sie hier gilt. Delegierte in der C # -Sprache hatten so lange Kovarianz und Kontravarianz, dass es sich nur "intrinsisch" anfühlt, aber nicht. Ich weiß nicht, dass es möglicherweise sogar in der CLR aktiviert ist, aber Visual Basic .NET hat für seine Delegaten erst mit .NET Framework 3.0 (VB.NET 2008) Kovarianz- und Kontravarianzfunktionen erhalten. Infolgedessen kann Visual Basic.NET für .NET 2.0 und niedriger diesen Ansatz nicht verwenden.

Das obige Beispiel kann beispielsweise wie folgt in VB.NET übersetzt werden:

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 kann zu 100% ausgeführt werden. Aber ich habe es jetzt auf VB.NET 2005 getestet, nur um sicherzugehen, und es wird nicht kompiliert.

Die Methode 'Public Sub SomeEventHandler (Absender als Objekt, e als vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' hat nicht dieselbe Signatur wie der Delegat 'Delegate Sub StrongTypedEventHandler (von TSender, TEventArgs als System.EventArgs) (Absender als Herausgeber, e als PublisherEvent) '

Grundsätzlich sind Delegaten in VB.NET-Versionen 2005 und niedriger unveränderlich. Ich habe vor ein paar Jahren tatsächlich an diese Idee gedacht, aber die Unfähigkeit von VB.NET, damit umzugehen, hat mich gestört ... Aber ich bin jetzt fest zu C # übergegangen, und VB.NET kann jetzt damit umgehen dieser Beitrag.

Bearbeiten: Update # 3

Ok, ich benutze das jetzt schon eine Weile ziemlich erfolgreich. Es ist wirklich ein schönes System. Ich habe beschlossen, meinen "StrongTypedEventHandler" als "GenericEventHandler" zu bezeichnen, der wie folgt definiert ist:

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

Abgesehen von dieser Umbenennung habe ich sie genau wie oben beschrieben implementiert.

Es wird über die FxCop-Regel CA1009 ausgelöst, die besagt:

"Konventionell haben .NET-Ereignisse zwei Parameter, die den Ereignisabsender und die Ereignisdaten angeben. Ereignishandlersignaturen sollten folgendermaßen aussehen: void MyEventHandler (Objektabsender, EventArgs e). Der Parameter 'sender' ist immer vom Typ System.Object, Selbst wenn es möglich ist, einen spezifischeren Typ zu verwenden. Der Parameter 'e' ist immer vom Typ System.EventArgs. Ereignisse, die keine Ereignisdaten bereitstellen, sollten den Delegatentyp System.EventHandler verwenden. Ereignishandler geben void zurück, damit sie senden können jedes Ereignis an mehrere Zielmethoden. Jeder von einem Ziel zurückgegebene Wert geht nach dem ersten Aufruf verloren. "

Natürlich wissen wir das alles und brechen trotzdem die Regeln. (Alle Ereignishandler können in jedem Fall den Standard-Objektabsender in ihrer Signatur verwenden, wenn dies bevorzugt wird. Dies ist eine unveränderliche Änderung.)

Die Verwendung von a SuppressMessageAttributemacht also den Trick:

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

Ich hoffe, dass dieser Ansatz irgendwann in der Zukunft zum Standard wird. Es funktioniert wirklich sehr gut.

Vielen Dank für all Ihre Meinungen, Leute, ich weiß das wirklich zu schätzen ...

Mike

Mike Rosenblum
quelle
6
Tu es. (Denken Sie nicht, dass dies eine Antwort rechtfertigt.)
Konrad Rudolph
1
Meine Argumente waren nicht wirklich auf Sie gerichtet: Natürlich sollten Sie dies in Ihren eigenen Projekten tun. Sie waren Argumente, warum es in der BCL möglicherweise nicht funktioniert.
Tommy Carlier
3
Mann, ich wünschte, mein Projekt hätte dies von Anfang an getan, ich hasse es, den Absender zu besetzen.
Matt H
7
Jetzt DIES ist eine Frage. Sehen Sie, Leute? Nicht eine dieser oh hi this my hom work solve it plz :code dump:Fragen in Tweet-Größe , sondern eine Frage , aus der wir lernen .
Camilo Martin
3
Ein weiterer Vorschlag, nennen Sie es einfach EventHandler<,>als GenericEventHandler<,>. EventHandler<>In BCL gibt es bereits Generika, die nur EventHandler heißen. EventHandler ist also ein gebräuchlicherer Name und Delegierte unterstützen
Typüberladungen

Antworten:

25

Es scheint, dass Microsoft dies aufgegriffen hat, da ein ähnliches Beispiel jetzt auf MSDN verfügbar ist:

Generische Delegierte

Bas
quelle
2
+1 Ah, ausgezeichnet. Sie haben dies tatsächlich aufgegriffen. Das ist gut. Ich hoffe jedoch, dass sie dies zu einem anerkannten Muster innerhalb der VS-IDE machen, da es derzeit schwieriger ist, dieses Muster in Bezug auf IntelliSense usw. zu verwenden.
Mike Rosenblum
13

Was Sie vorschlagen, macht tatsächlich viel Sinn, und ich frage mich nur, ob dies eines dieser Dinge ist, das einfach so ist, weil es ursprünglich vor Generika entworfen wurde, oder ob es einen wirklichen Grund dafür gibt.

BKostenlos
quelle
1
Ich bin sicher, dass dies genau der Grund ist. Da die neueren Versionen der Sprache jedoch Kontravarianzen aufweisen, um damit umzugehen, sollten sie in der Lage sein, dies abwärtskompatibel zu handhaben. Frühere Handler, die ein Absenderobjekt verwenden, wurden nicht beschädigt. Dies gilt jedoch nicht für ältere Sprachen und möglicherweise nicht für einige aktuelle .NET-Sprachen. Ich bin mir nicht sicher.
Mike Rosenblum
13

Die Windows-Laufzeit (WinRT) führt einen TypedEventHandler<TSender, TResult>Delegaten ein, der genau das tut, was Sie StrongTypedEventHandler<TSender, TResult>tun, aber anscheinend ohne die Einschränkung des TResultTypparameters:

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

Die MSDN-Dokumentation finden Sie hier .

Pierre Arnaud
quelle
1
Ah, schön zu sehen, dass es Fortschritte gibt ... Ich frage mich, warum TResult nicht darauf beschränkt ist, von der 'EventArgs'-Klasse zu erben. Die Basisklasse 'EventArgs' ist grundsätzlich leer. Vielleicht entfernen sie sich von dieser Einschränkung?
Mike Rosenblum
Es könnte ein Versehen des Designteams sein; Wer weiß.
Pierre Arnaud
Nun, Events funktionieren gut ohne Verwendung EventArgs, es ist nur eine Konventionssache
Sebastian
3
In der TypedEventHandler-Dokumentation argswird ausdrücklich angegeben, dass nullkeine Ereignisdaten vorhanden sind. Es scheint also, dass sie standardmäßig kein im Wesentlichen leeres Objekt verwenden. Ich vermute, dass die ursprüngliche Idee war, dass eine Methode mit einem zweiten Parameter vom Typ EventArgsjedes Ereignis verarbeiten kann, da die Typen immer kompatibel sind. Sie erkennen jetzt wahrscheinlich, dass es nicht so wichtig ist, mehrere verschiedene Ereignisse mit einer Methode verarbeiten zu können.
jmcilhinney
1
Es sieht nicht nach einem Versehen aus. Die Einschränkung wurde auch aus dem System.EventHandler <TEventArgs> -Delegat entfernt. referencesource.microsoft.com/#mscorlib/system/…
colton7909
5

Ich habe Probleme mit den folgenden Aussagen:

  • Ich glaube, dass die älteren Versionen von Visual Basic .NET bis 2005 keine delegierte Kovarianz und Kontravarianz aufweisen.
  • Mir ist völlig klar, dass dies an Blasphemie grenzt.

Erstens hat nichts, was Sie hier getan haben, etwas mit Kovarianz oder Kontravarianz zu tun. ( Bearbeiten: Die vorherige Aussage ist falsch. Weitere Informationen finden Sie unter Kovarianz und Kontravarianz in Delegaten. ) Diese Lösung funktioniert in allen CLR-Versionen 2.0 und höher einwandfrei (dies funktioniert natürlich nicht in einer CLR 1.0-Anwendung, da Generika verwendet werden).

Zweitens stimme ich überhaupt nicht zu, dass Ihre Idee an "Blasphemie" grenzt, da dies eine wunderbare Idee ist.

Andrew Hare
quelle
2
Hallo Andrew, danke für die Daumen hoch! In Anbetracht Ihres Reputationsniveaus bedeutet mir das wirklich viel ... Zum Thema Kovarianz / Kontravarianz: Wenn der vom Abonnenten bereitgestellte Delegierte nicht genau mit der Signatur des Ereignisses des Herausgebers übereinstimmt, sind Kovarianz und Kontravarianz beteiligt. C # hat seit Ewigkeiten delegierte Kovarianz und Kontravarianz, daher fühlt es sich intrinsisch an, aber VB.NET hatte bis .NET 3.0 keine delegierte Kovarianz und Kontravarianz. Daher kann VB.NET für .NET 2.0 und niedriger dieses System nicht verwenden. (Siehe das Codebeispiel, das ich oben in "Edit # 2" hinzugefügt habe.)
Mike Rosenblum
@ Mike - Ich entschuldige mich, Sie sind 100% richtig! Ich habe meine Antwort bearbeitet, um Ihren Standpunkt widerzuspiegeln :)
Andrew Hare
4
Ah, interessant! Es scheint, dass delegierte Kovarianz / Kontravarianz Teil der CLR ist, aber (aus Gründen, die ich nicht kenne) wurde sie von VB.NET vor der neuesten Version nicht verfügbar gemacht. Hier ist ein Artikel von Francesco Balena, der zeigt, wie Delegatenvarianz mit Reflection erreicht werden kann, wenn sie nicht durch die Sprache selbst aktiviert wird: dotnet2themax.com/blogs/fbalena/… .
Mike Rosenblum
1
@Mike - Es ist immer interessant zu lernen, was die CLR unterstützt, aber in keiner der .NET-Sprachen unterstützt.
Andrew Hare
5

Ich warf einen Blick darauf, wie dies mit dem neuen WinRT und basierend auf anderen Meinungen hier gehandhabt wurde, und entschied mich schließlich dafür, es so zu machen:

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

Dies scheint der beste Weg zu sein, wenn man den Namen TypedEventHandler in WinRT verwendet.

Inverness
quelle
Warum die generische Einschränkung für TEventArgs hinzufügen? Es wurde aus EventHandler <> und TypedEventHandler <,> entfernt, da es keinen Sinn ergab.
Mike Marynowski
2

Ich denke, es ist eine großartige Idee, und MS hat möglicherweise einfach nicht die Zeit oder das Interesse, in eine Verbesserung zu investieren, wie zum Beispiel beim Wechsel von ArrayList zu generischen Listen.

Otávio Décio
quelle
Sie könnten Recht haben ... Andererseits denke ich, dass es nur ein "Standard" ist und möglicherweise überhaupt kein technisches Problem. Das heißt, diese Funktion ist möglicherweise in allen aktuellen .NET-Sprachen vorhanden, ich weiß es nicht. Ich weiß, dass C # und das VB.NET damit umgehen können. Ich bin mir jedoch nicht sicher, wie allgemein dies in allen aktuellen .NET-Sprachen funktioniert ... Da es jedoch in C # und VB.NET funktioniert und alle hier so unterstützend sind, denke ich, dass ich dies sehr wahrscheinlich tun werde. :-)
Mike Rosenblum
2

Soweit ich weiß, soll sich das Feld "Absender" immer auf das Objekt beziehen, das das Ereignisabonnement enthält. Wenn ich meine Dr. Wenn einer die eigentliche Arbeit erledigt und die eigentlichen Daten enthält und der andere einen öffentlichen Schnittstellen-Wrapper bereitstellt, könnte der Hauptteil einen schwachen Verweis auf den Wrapper-Teil enthalten. Wenn der Wrapper-Teil durch Müll gesammelt wird, würde dies bedeuten Es war niemand mehr an den gesammelten Daten interessiert, und der Änderungslogger sollte sich daher von jedem empfangenen Ereignis abmelden.

Da es möglich ist, dass ein Objekt Ereignisse im Namen eines anderen Objekts sendet, kann ich einen potenziellen Nutzen erkennen, wenn ein "Absender" -Feld vom Objekttyp vorhanden ist und das von EventArgs abgeleitete Feld einen Verweis auf das Objekt enthält, das sollte gehandelt werden. Die Nützlichkeit des Felds "Absender" wird jedoch wahrscheinlich durch die Tatsache eingeschränkt, dass es keine saubere Möglichkeit für ein Objekt gibt, sich von einem unbekannten Absender abzumelden.

(*) Eine sauberere Methode zur Behandlung von Abmeldungen wäre ein Multicast-Delegatentyp für Funktionen, die Boolean zurückgeben. Wenn eine von einem solchen Delegaten aufgerufene Funktion True zurückgibt, wird der Delegat gepatcht, um dieses Objekt zu entfernen. Dies würde bedeuten, dass Delegaten nicht mehr wirklich unveränderlich sind, aber es sollte möglich sein, eine solche Änderung threadsicher durchzuführen (z. B. indem die Objektreferenz auf Null gesetzt wird und der Multicast-Delegatencode alle eingebetteten Nullobjektreferenzen ignoriert). In diesem Szenario kann ein Versuch, ein veröffentlichtes Objekt zu veröffentlichen und ein Ereignis zu erstellen, sehr sauber behandelt werden, unabhängig davon, woher das Ereignis stammt.

Superkatze
quelle
2

Wenn man auf Blasphemie als einzigen Grund zurückblickt, Absender zu einem Objekttyp zu machen (um Probleme mit Kontravarianzen im VB 2005-Code, der meiner Meinung nach ein Fehler von Microsoft ist, wegzulassen), kann jemand zumindest ein theoretisches Motiv vorschlagen, um das zweite Argument dem EventArgs-Typ zuzuordnen. Gibt es in diesem speziellen Fall einen guten Grund, die Richtlinien und Konventionen von Microsoft einzuhalten?

Es scheint seltsam, einen anderen EventArgs-Wrapper für andere Daten entwickeln zu müssen, die wir im Event-Handler übergeben möchten. Warum können diese Daten dort nicht direkt übergeben werden? Betrachten Sie die folgenden Codeabschnitte

[Beispiel 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;
}

[Beispiel 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;
}
Lu4
quelle
2
Ja, das Erstellen einer separaten Klasse, die von System.EventArgs erbt, kann nicht intuitiv erscheinen und stellt zusätzliche Arbeit dar, aber es gibt einen sehr guten Grund dafür. Wenn Sie Ihren Code nie ändern müssen, ist Ihr Ansatz in Ordnung. Die Realität ist jedoch, dass Sie möglicherweise die Funktionalität des Ereignisses in einer zukünftigen Version erhöhen und den Ereignisargumenten Eigenschaften hinzufügen müssen. In Ihrem Szenario müssten Sie der Signatur des Ereignishandlers zusätzliche Überladungen oder optionale Parameter hinzufügen. Dies ist ein Ansatz, der in VBA und Legacy-VB 6.0 verwendet wird und zwar praktikabel, in der Praxis jedoch etwas hässlich ist.
Mike Rosenblum
1
Durch das Erben von EventArgs kann jedoch eine zukünftige Version von Ihrer älteren Ereignisargumentklasse erben und diese erweitern. Alle älteren Aufrufer können weiterhin genau so arbeiten, wie sie sind, indem sie mit der Basisklasse Ihrer neuen Ereignisargumentklasse arbeiten. Sehr sauber. Mehr Arbeit für Sie, aber sauberer für alle Anrufer, die von Ihrer Bibliothek abhängen.
Mike Rosenblum
Es muss nicht einmal geerbt werden. Sie können die zusätzlichen Funktionen einfach direkt in Ihre Event-Args-Klasse einfügen und es funktioniert weiterhin einwandfrei. Das heißt, die Beschränkung, die Argumente auf Eventargs fixiert, wurde entfernt, da dies für viele Szenarien nicht viel Sinn machte, d. H. Wenn Sie wissen, dass Sie die Funktionalität eines bestimmten Ereignisses niemals erweitern müssen, oder wenn Sie in sehr leistungsempfindlichen Anwendungen nur einen Werttyp arg benötigen.
Mike Marynowski
1

In der aktuellen Situation (Absender ist Objekt) können Sie problemlos eine Methode an mehrere Ereignisse anhängen:

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

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

Wenn der Absender generisch wäre, wäre das Ziel des Klickereignisses nicht vom Typ Button oder Label, sondern vom Typ Control (da das Ereignis in Control definiert ist). Einige Ereignisse in der Button-Klasse hätten also ein Ziel vom Typ Control, andere andere Zieltypen.

Tommy Carlier
quelle
2
Tommy, mit dem von mir vorgeschlagenen System können Sie genau dasselbe tun. Sie können weiterhin einen Standard-Ereignishandler verwenden, der über einen 'Objektabsender'-Parameter verfügt, um diese stark typisierten Ereignisse zu verarbeiten. (Siehe das Codebeispiel, das ich jetzt zum ursprünglichen Beitrag hinzugefügt habe.)
Mike Rosenblum
Ja, ich stimme zu, dies ist das Gute an Standard-.NET-Ereignissen, akzeptiert!
Lu4
1

Ich glaube nicht, dass irgendetwas falsch ist mit dem, was du tun willst. Ich vermute größtenteils, dass der object senderParameter erhalten bleibt, um weiterhin Code vor 2.0 zu unterstützen.

Wenn Sie diese Änderung wirklich für eine öffentliche API vornehmen möchten, sollten Sie eine eigene Basis-EvenArgs-Klasse erstellen. Etwas wie das:

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; } }
}

Dann können Sie Ihre Ereignisse so deklarieren

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

Und Methoden wie diese:

private void HandleSomething(object sender, EventArgs e)

wird weiterhin abonnieren können.

BEARBEITEN

Diese letzte Zeile hat mich ein wenig nachdenken lassen ... Sie sollten tatsächlich in der Lage sein, das, was Sie vorschlagen, zu implementieren, ohne externe Funktionen zu beeinträchtigen, da die Laufzeit kein Problem mit Downcasting-Parametern hat. Ich würde mich immer noch DataEventArgs(persönlich) zur Lösung neigen. Ich würde dies jedoch tun, obwohl ich weiß, dass es redundant ist, da der Absender im ersten Parameter und als Eigenschaft der Ereignisargumente gespeichert ist.

Ein Vorteil des Festhaltens an DataEventArgsbesteht darin, dass Sie Ereignisse verketten und den Absender ändern können (um den letzten Absender darzustellen), während die EventArgs den ursprünglichen Absender beibehalten.

Michael Meadows
quelle
Hey Michael, das ist eine ziemlich nette Alternative. Ich mag das. Wie Sie bereits erwähnt haben, ist es jedoch überflüssig, den Parameter "Absender" zweimal effektiv übergeben zu lassen. Ein ähnlicher Ansatz wird hier diskutiert: stackoverflow.com/questions/809609/… , und der Konsens scheint zu sein, dass er zu nicht dem Standard entspricht. Aus diesem Grund habe ich gezögert, hier die stark typisierte Absenderidee vorzuschlagen. (Scheint aber gut aufgenommen zu werden, also freue ich mich.)
Mike Rosenblum
1

Tue es. Bei nicht komponentenbasiertem Code vereinfache ich Ereignissignaturen häufig einfach

public event Action<MyEventType> EventName

wo MyEventTypeerbt nicht von EventArgs. Warum sich die Mühe machen, wenn ich nie beabsichtige, eines der Mitglieder von EventArgs zu verwenden?

Scott Weinstein
quelle
1
Zustimmen! Warum sollten wir uns als Affen fühlen?
Lu4
1
+ 1-ed, das benutze ich auch. Manchmal gewinnt die Einfachheit! Oder sogar event Action<S, T>, event Action<R, S, T>usw. Ich habe eine Erweiterungsmethode , um Raisesie Thread-sicher :)
Nawfal