Ereignissignatur in .NET - Verwenden eines stark typisierten Absenders?

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

(1) Sollte ich erwägen, dies für meine eigene Entwicklungsarbeit zu verwenden, die zu 100% für interne Zwecke 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 Standard-Ereignissignatur zu verwenden, die so aussieht:

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

Ich erwäge die Verwendung einer Ereignissignatur, die einen stark typisierten 'Absender'-Parameter verwendet, wie folgt:

Definieren Sie zunächst einen "StrongTypedEventHandler":

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

Dies unterscheidet sich nicht allzu sehr von einer Aktion <TSender, TEventArgs>, sondern durch die Verwendung derStrongTypedEventHandlererzwingen wir, dass die TEventArgs von abgeleitet sindSystem.EventArgs.

Als Nächstes können wir als Beispiel den StrongTypedEventHandler in einer Veröffentlichungsklasse wie folgt verwenden:

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

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

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

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

Mir ist völlig klar, dass dies mit dem standardmäßigen .NET-Ereignisbehandlungsmuster bricht. Bedenken Sie jedoch, dass ein Abonnent bei einer Abweichung eine herkömmliche Signatur für die Ereignisbehandlung verwenden kann, wenn dies gewünscht wird:

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

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 Konventionen gebrochen habe (was ich nicht leicht nehme, glaube mir), kann ich mir keine Nachteile vorstellen.

Möglicherweise gibt es hier einige Probleme mit der CLS-Konformität. Dies funktioniert in Visual Basic .NET 2008 zu 100% einwandfrei (ich habe es getestet), aber ich glaube, dass die älteren Versionen von Visual Basic .NET bis 2005 keine Kovarianz und Kontravarianz aufweisen.[Bearbeiten: Ich habe dies seitdem getestet und es ist bestätigt: VB.NET 2005 und darunter 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, die ebenfalls ein Problem damit haben. Ich bin mir nicht sicher.

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

Kann sich noch jemand ein Problem damit vorstellen? Oder verstößt dies einfach so sehr gegen die Konvention, dass sich die Mägen der Menschen drehen?

Hier sind einige verwandte Links, die ich gefunden habe:

(1)Richtlinien für das Eventdesign [MSDN 3.5]

(2)C # -Einfache Ereignisauslösung - 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

Bearbeiten Sie # 1: Dies ist eine Antwort aufBeitrag von Tommy Carlier:

In diesem vollständigen Beispiel wird gezeigt, dass sowohl stark typisierte Ereignishandler als auch die aktuellen Standardereignishandler, die einen Parameter 'object sender' verwenden, mit diesem Ansatz koexistieren können. Sie können den Code kopieren, einfügen und ausführen:

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

Bearbeiten Sie # 2: Dies ist eine Antwort aufAndrew Hares Aussage in Bezug auf Kovarianz und Kontravarianz und wie es hier gilt. Delegierte in der C # -Sprache haben so lange Kovarianz und Kontravarianz, dass sie sich nur "intrinsisch" anfühlen, aber nicht. Möglicherweise ist dies sogar in der CLR aktiviert, ich weiß es nicht, aber Visual Basic .NET konnte bis .NET Framework 3.0 (VB.NET 2008) keine Kovarianz- und Kontravarianzfunktionen für seine Delegierten bereitstellen. Infolgedessen kann Visual Basic.NET für .NET 2.0 und höher diesen Ansatz nicht verwenden.

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

<code>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
</code>

VB.NET 2008 kann es zu 100% ausführen. 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, und als vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' hat nicht die gleiche Signatur wie der Delegat 'Delegate Sub StrongTypedEventHandler (Of TSender, TEventArgs As System.EventArgs) (Absender als Herausgeber als Herausgeber) '

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

Bearbeiten: Update Nr. 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:

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

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

Es löst die FxCop-Regel CA1009 aus, in der Folgendes angegeben ist:

Konventionell haben .NET-Ereignisse zwei Parameter, die den Ereignissender und die Ereignisdaten angeben. Ereignishandlersignaturen sollten folgendermaßen aussehen: void MyEventHandler (Objektabsender, EventArgs e). Der Parameter 'sender' hat immer den Typ System.Object. Auch 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. Event-Handler geben void zurück, damit sie senden können Jedes Ereignis wird mehreren Zielmethoden zugewiesen. 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 Event-Handler können auf jeden Fall das standardmäßige 'Objekt Sender' in ihrer Signatur verwenden - dies ist eine nicht störende Änderung.)

Also die Verwendung von aSuppressMessageAttribute macht den Trick:

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

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

Danke für all eure Meinungen, Leute, ich weiß das wirklich zu schätzen ...

Mike

Antworten auf die Frage(11)

Ihre Antwort auf die Frage