Gibt es einen Unterschied zwischen deklarieren event Action<>
und event EventHandler<>
.
Angenommen, es spielt keine Rolle, welches Objekt tatsächlich ein Ereignis ausgelöst hat.
beispielsweise:
public event Action<bool, int, Blah> DiagnosticsEvent;
vs.
public event EventHandler<DiagnosticsArgs> DiagnosticsEvent;
class DiagnosticsArgs : EventArgs
{
public DiagnosticsArgs(bool b, int i, Blah bl)
{...}
...
}
Die Nutzung wäre in beiden Fällen nahezu gleich:
obj.DiagnosticsEvent += HandleDiagnosticsEvent;
Es gibt einige Dinge, die ich an event EventHandler<>
Mustern nicht mag :
- Zusätzliche Typdeklaration von EventArgs abgeleitet
- Zwangsübergabe der Objektquelle - oft kümmert es niemanden
Mehr Code bedeutet mehr Code, der ohne klaren Vorteil gepflegt werden muss.
Daher bevorzuge ich event Action<>
Nur wenn in Aktion <> zu viele Typargumente vorhanden sind, ist eine zusätzliche Klasse erforderlich.
Antworten:
Der Hauptunterschied besteht darin, dass bei Verwendung
Action<>
Ihres Ereignisses das Entwurfsmuster praktisch aller anderen Ereignisse im System nicht eingehalten wird, was ich als Nachteil betrachten würde.Ein Vorteil des dominierenden Entwurfsmusters (abgesehen von der Kraft der Gleichheit) ist, dass Sie das
EventArgs
Objekt mit neuen Eigenschaften erweitern können, ohne die Signatur des Ereignisses zu ändern. Dies wäre immer noch möglich, wenn Sie es verwenden würdenAction<SomeClassWithProperties>
, aber ich sehe keinen Sinn darin, in diesem Fall nicht den regulären Ansatz zu verwenden.quelle
Action<>
zu Speicherverlusten führen? Ein Nachteil desEventHandler
Entwurfsmusters sind Speicherlecks. Es sollte auch darauf hingewiesen werden, dass es mehrere Event-Handler geben kann, aber nur eine AktionAction<T>
. AußerdemAction<T>
kann auf mehrere Methoden verwiesen werden. Hier ist eine Zusammenfassung, die dies demonstriert: gist.github.com/fmork/4a4ddf687fa8398d19ddb2df96f0b434Basierend auf einigen der vorherigen Antworten werde ich meine Antwort in drei Bereiche unterteilen.
Erstens physikalische Einschränkungen bei der Verwendung im
Action<T1, T2, T2... >
Vergleich zur Verwendung einer abgeleiteten Klasse vonEventArgs
. Es gibt drei Möglichkeiten: Erstens: Wenn Sie die Anzahl oder den Typ der Parameter ändern, muss jede abonnierte Methode geändert werden, um dem neuen Muster zu entsprechen. Wenn dies ein öffentlich zugängliches Ereignis ist, das Assemblys von Drittanbietern verwenden, und die Möglichkeit besteht, dass sich die Ereignisargumente ändern, ist dies ein Grund, aus Gründen der Konsistenz eine benutzerdefinierte Klasse zu verwenden, die von Ereignisargumenten abgeleitet ist (denken Sie daran, dass Sie dies immer noch tun KÖNNEN) use anAction<MyCustomClass>
) ZweitensAction<T1, T2, T2... >
verhindert using , dass Sie Feedback an die aufrufende Methode zurückgeben, es sei denn, Sie haben ein Objekt (z. B. mit einer Handled-Eigenschaft), das zusammen mit der Aktion übergeben wird. Drittens Sie keine Parameter bekommen genannt, wenn Sie also 3 vorbei sindbool
‚s einint
, zweistring
und aDateTime
, Sie haben keine Ahnung, was die Bedeutung dieser Werte ist. Als Randnotiz können Sie immer noch die Methode "Dieses Ereignis sicher auslösen, während Sie es noch verwendenAction<T1, T2, T2... >
" verwenden.Zweitens Auswirkungen auf die Konsistenz. Wenn Sie ein großes System haben, mit dem Sie bereits arbeiten, ist es fast immer besser, dem Design des restlichen Systems zu folgen, es sei denn, Sie haben einen sehr guten Grund, dies nicht zu tun. Wenn Sie öffentlich mit Ereignissen konfrontiert sind, die beibehalten werden müssen, kann die Möglichkeit, abgeleitete Klassen zu ersetzen, wichtig sein. Vergiss das nicht.
Drittens, in der Praxis, stelle ich persönlich fest, dass ich dazu neige, viele einmalige Ereignisse für Dinge wie Eigenschaftsänderungen zu erstellen, mit denen ich interagieren muss (insbesondere bei MVVM mit Ansichtsmodellen, die miteinander interagieren) oder wo das Ereignis stattgefunden hat ein einzelner Parameter. Meistens nehmen diese Ereignisse die Form von
public event Action<[classtype], bool> [PropertyName]Changed;
oder anpublic event Action SomethingHappened;
. In diesen Fällen gibt es zwei Vorteile. Zuerst bekomme ich einen Typ für die ausstellende Klasse. WennMyClass
deklariert und die einzige Klasse ist, die das Ereignis auslöst, erhalte ich eine explizite InstanzMyClass
, mit der ich im Ereignishandler arbeiten kann. Zweitens ist für einfache Ereignisse wie Eigenschaftsänderungsereignisse die Bedeutung der Parameter offensichtlich und wird im Namen des Ereignishandlers angegeben, und ich muss nicht unzählige Klassen für diese Art von Ereignissen erstellen.quelle
Zum größten Teil würde ich sagen, folgen Sie dem Muster. Ich bin davon abgewichen, aber sehr selten und aus bestimmten Gründen. In diesem Fall ist das größte Problem, das ich haben würde, dass ich wahrscheinlich immer noch ein verwenden würde
Action<SomeObjectType>
, wodurch ich später zusätzliche Eigenschaften hinzufügen und gelegentlich die 2-Wege-Eigenschaft (ThinkHandled
oder andere Feedback-Ereignisse, bei denen die Der Abonnent muss eine Eigenschaft für das Ereignisobjekt festlegen . Und wenn Sie einmal damit angefangen haben, können Sie es auchEventHandler<T>
für einige verwendenT
.quelle
Der Vorteil eines wortreicheren Ansatzes ergibt sich, wenn sich Ihr Code in einem 300.000-Zeilen-Projekt befindet.
Wenn Sie die Aktion verwenden, wie Sie es getan haben, können Sie mir nicht sagen, was Bool, Int und Blah sind. Wenn Ihre Aktion ein Objekt übergeben hat, das die Parameter definiert hat, ist dies in Ordnung.
Wenn Sie einen EventHandler verwenden, der ein EventArgs haben möchte, und wenn Sie Ihr DiagnosticsArgs-Beispiel mit Gettern für die Eigenschaften vervollständigen würden, die ihren Zweck kommentiert haben, ist Ihre Anwendung verständlicher. Bitte kommentieren oder benennen Sie die Argumente im DiagnosticsArgs-Konstruktor vollständig.
quelle
Wenn Sie dem Standardereignismuster folgen, können Sie eine Erweiterungsmethode hinzufügen, um die Überprüfung des Auslösens von Ereignissen sicherer / einfacher zu machen. (dh der folgende Code fügt eine Erweiterungsmethode namens SafeFire () hinzu, die die Nullprüfung durchführt und (offensichtlich) das Ereignis in eine separate Variable kopiert, um vor der üblichen Null-Race-Bedingung, die Ereignisse beeinflussen kann, sicher zu sein.)
(Obwohl ich mir nicht sicher bin, ob Sie Erweiterungsmethoden für Nullobjekte verwenden sollten ...)
quelle
Mir ist klar, dass diese Frage über 10 Jahre alt ist, aber es scheint mir, dass nicht nur die offensichtlichste Antwort nicht angesprochen wurde, sondern dass aus der Frage möglicherweise nicht wirklich ein gutes Verständnis dafür hervorgeht, was unter der Decke vor sich geht. Darüber hinaus gibt es weitere Fragen zur verspäteten Bindung und was dies für Delegierte und Lambdas bedeutet (dazu später mehr).
Sprechen Sie zuerst den 800 Pfund schweren Elefanten / Gorilla im Raum an, wenn Sie
event
vsAction<T>
/ wählen möchtenFunc<T>
:event
Sie diese Option, wenn Sie mehr von einem Pub / Sub-Modell mit mehreren Anweisungen / Lambdas / Funktionen möchten, die ausgeführt werden (dies ist auf Anhieb ein großer Unterschied).Lassen Sie uns als Beispiel für ein Ereignis eine einfache und standardmäßige Reihe von Ereignissen mithilfe einer kleinen Konsolenanwendung wie folgt verkabeln:
Die Ausgabe sieht wie folgt aus:
Wenn Sie dasselbe mit
Action<int>
oder tunAction<object, SomeStandardArgs>
würden, würden Sie nurSomeSimpleEvent2
und sehenSomeStandardEvent2
.Also, was ist in dir los
event
?Wenn wir erweitern
FireNiceEvent
, generiert der Compiler tatsächlich Folgendes (ich habe einige Details in Bezug auf die Thread-Synchronisation weggelassen, die für diese Diskussion nicht relevant sind):Der Compiler generiert eine private Delegatenvariable, die für den Klassennamensraum, in dem sie generiert wird, nicht sichtbar ist. Dieser Delegat wird für das Abonnementmanagement und die verspätete Teilnahme verwendet, und die öffentlich zugängliche Oberfläche ist die vertraute
+=
und die-=
Betreiber, die wir alle kennen und lieben gelernt haben :)Sie können den Code für die Handler zum Hinzufügen / Entfernen anpassen, indem Sie den Bereich des
FireNiceEvent
Delegaten in "Geschützt" ändern . Auf diese Weise können Entwickler den Hooks jetzt benutzerdefinierte Hooks hinzufügen, z. B. Protokollierungs- oder Sicherheitshooks. Dies führt zu einigen sehr leistungsstarken Funktionen, die jetzt einen benutzerdefinierten Zugriff auf Abonnements basierend auf Benutzerrollen usw. ermöglichen. Können Sie dies mit Lambdas tun? (Eigentlich können Sie Ausdrucksbäume benutzerdefiniert kompilieren, aber das geht über den Rahmen dieser Antwort hinaus).Um einige Punkte aus einigen der Antworten hier anzusprechen:
Es gibt wirklich keinen Unterschied in der 'Sprödigkeit' zwischen dem Ändern der Args-Liste in
Action<T>
und dem Ändern der Eigenschaften in einer von abgeleiteten KlasseEventArgs
. Beide erfordern nicht nur eine Kompilierungsänderung, sondern auch eine öffentliche Schnittstelle und eine Versionierung. Kein Unterschied.In Bezug darauf, welcher Industriestandard ist, hängt dies davon ab, wo und warum dieser verwendet wird.
Action<T>
und solche werden häufig in IoC und DI verwendet undevent
werden häufig in Nachrichtenrouting wie GUI- und MQ-Frameworks verwendet. Beachten Sie, dass ich oft gesagt habe , nicht immer .Delegierte haben andere Lebensdauern als Lambdas. Man muss sich auch der Gefangennahme bewusst sein ... nicht nur mit dem Schließen, sondern auch mit dem Gedanken "Schau, was die Katze hineingezogen hat". Dies wirkt sich sowohl auf den Speicherbedarf / die Lebensdauer als auch auf die Verwaltung aus.
Eine weitere Sache, auf die ich bereits hingewiesen habe ... der Begriff der späten Bindung. Sie werden dies häufig sehen, wenn Sie ein Framework wie LINQ verwenden, wenn ein Lambda "live" wird. Dies unterscheidet sich stark von der späten Bindung eines Delegierten, die mehr als einmal auftreten kann (dh das Lambda ist immer vorhanden, die Bindung erfolgt jedoch bei Bedarf so oft wie erforderlich), im Gegensatz zu einem Lambda, das, sobald es auftritt, durchgeführt wird - Die Magie ist verschwunden und die Methode (n) / Eigenschaft (en) werden immer gebunden. Etwas zu beachten.
quelle
Mit Blick auf Standard .NET-Ereignismuster finden wir
Unten auf derselben Seite finden wir ein Beispiel für die typische Ereignisdefinition, die so etwas wie ist
Hatten wir definiert
der Handler hätte sein können
wo
sender
hat den richtigen ( mehr abgeleiteten ) Typ.quelle
sender
.