Gemäß den Microsoft-Richtlinien zur Benennung von Ereignissen ist der sender
Parameter in einem C # -Ereignishandler " immer vom Typ Objekt, auch wenn es möglich ist, einen spezifischeren Typ zu verwenden".
Dies führt zu vielen Ereignisbehandlungscodes wie:
RepeaterItem item = sender as RepeaterItem;
if (item != null) { /* Do some stuff */ }
Warum rät die Konvention davon ab, einen Event-Handler mit einem spezifischeren Typ zu deklarieren?
MyType
{
public event MyEventHander MyEvent;
}
...
delegate void MyEventHander(MyType sender, MyEventArgs e);
Vermisse ich eine Gotcha?
Für die Nachwelt: Ich bin mit der allgemeinen Stimmung in den Antworten , dass die Konvention ist zu genutztes Objekt (und Daten über das passieren EventArgs
) , selbst wenn es möglich ist , eine bestimmte Art zu verwenden, und in der realen Welt der Programmierung es ist wichtig , zu folgen Das Treffen.
Bearbeiten: Köder für die Suche: RSPEC-3906-Regel "Event-Handler sollten die richtige Signatur haben"
Antworten:
Nun, es ist eher ein Muster als eine Regel. Dies bedeutet, dass eine Komponente ein Ereignis von einer anderen weiterleiten kann, wobei der ursprüngliche Absender beibehalten wird, auch wenn es sich nicht um den normalen Typ handelt, der das Ereignis auslöst.
Ich bin damit einverstanden, dass es ein bisschen seltsam ist - aber es lohnt sich wahrscheinlich, sich nur aus Gründen der Vertrautheit an die Konvention zu halten. (Vertrautheit mit anderen Entwicklern.) Ich war noch nie besonders scharf auf
EventArgs
mich selbst (da es für sich genommen keine Informationen vermittelt), aber das ist ein anderes Thema. (Zumindest haben wirEventHandler<TEventArgs>
jetzt - obwohl es hilfreich wäre, wenn es auch eineEventArgs<TContent>
für die allgemeine Situation gibt, in der Sie nur einen einzigen Wert benötigen, um weitergegeben zu werden.)BEARBEITEN: Dies macht den Delegaten natürlich allgemeiner - ein einzelner Delegatentyp kann für mehrere Ereignisse wiederverwendet werden. Ich bin mir nicht sicher, ob ich das als besonders guten Grund kaufe - besonders im Hinblick auf Generika -, aber ich denke, es ist etwas ...
quelle
"Generic delegates are especially useful in defining events based on the typical design pattern because the sender argument can be strongly typed and no longer has to be cast to and from Object."
von MSDNIch denke, es gibt einen guten Grund für diese Konvention.
Nehmen wir das Beispiel von @ erikkallen:
void SomethingChanged(object sender, EventArgs e) { EnableControls(); } ... MyRadioButton.Click += SomethingChanged; MyCheckbox.Click += SomethingChanged; MyDropDown.SelectionChanged += SomethingChanged; ...
Dies ist möglich (und seit .Net 1 vor Generika), da Kovarianz unterstützt wird.
Ihre Frage ist absolut sinnvoll, wenn Sie von oben nach unten gehen - dh Sie benötigen das Ereignis in Ihrem Code, also fügen Sie es Ihrer Kontrolle hinzu.
Die Konvention soll es jedoch einfacher machen, die Komponenten überhaupt zu schreiben. Sie wissen, dass für jedes Ereignis das Grundmuster (Objektabsender, EventArgs e) funktioniert.
Wenn Sie das Ereignis hinzufügen, wissen Sie nicht, wie es verwendet wird, und Sie möchten die Entwickler, die Ihre Komponente verwenden, nicht willkürlich einschränken.
Ihr Beispiel für ein generisches, stark typisiertes Ereignis ist in Ihrem Code sinnvoll, passt jedoch nicht zu anderen Komponenten, die von anderen Entwicklern geschrieben wurden. Zum Beispiel, wenn sie Ihre Komponente mit den oben genannten verwenden möchten:
//this won't work GallowayClass.Changed += SomethingChanged;
In diesem Beispiel verursacht die zusätzliche Typbeschränkung nur Schmerzen für den Remote-Entwickler. Sie müssen jetzt einen neuen Delegaten nur für Ihre Komponente erstellen. Wenn sie eine Last Ihrer Komponenten verwenden, benötigen sie möglicherweise einen Delegaten für jede einzelne.
Ich bin der Meinung, dass es sich lohnt, die Konvention für alles zu befolgen, was extern ist oder von dem Sie erwarten, dass es außerhalb eines engen Teams verwendet wird.
Ich mag die Idee der generischen Ereignisargumente - ich verwende bereits etwas Ähnliches.
quelle
RepeaterItem
) anfügt und dann ... was? Der Code trifft und versucht die Umwandlung, er gibt null zurück, dann passiert nichts ... Wenn der Handler nicht dafür ausgelegt ist, einen bestimmten Objekttyp als Absender zu behandeln, warum möchten Sie dann, dass er angehängt werden kann? Natürlich wird es Ereignisse geben, die allgemein genug sind, um die Verwendungobject
mit Ihrer Logik vollständig zu rechtfertigen , aber ... oft nicht so gut (?) Ich vermisse möglicherweise etwas, also zögern Sie nicht, zu beleuchten, was das sein könnte.EventArgs
Typ immer noch einen mit einemEventArgs
Parameter definierten Handler verwenden kann, kann auch ein Ereignis mit einem spezifischeren Absendertypobject
einen mit einemobject
Parameter definierten Handler verwenden . Ihr Beispiel für das, was nicht funktioniert, funktioniert einwandfrei.MyType
) an einenobject sender
Parameter übergeben. Wenn er jedoch einen expliziten Typ (z. B.ApiType sender
) hat, können Sie ihn nicht übergebenMyType
. Es gibt Gründe, warum Sie diese Einschränkung möglicherweise wünschen, und dies ist nicht der Fall schlechte Praxis, aber das OP fragte, warumobject sender
ein allgemeines Muster von Microsoft als Standard verwendet wurde.Ich verwende den folgenden Delegaten, wenn ich einen stark typisierten Absender bevorzugen würde.
/// <summary> /// Delegate used to handle events with a strongly-typed sender. /// </summary> /// <typeparam name="TSender">The type of the sender.</typeparam> /// <typeparam name="TArgs">The type of the event arguments.</typeparam> /// <param name="sender">The control where the event originated.</param> /// <param name="e">Any event arguments.</param> public delegate void EventHandler<TSender, TArgs>(TSender sender, TArgs e) where TArgs : EventArgs;
Dies kann auf folgende Weise verwendet werden:
public event EventHandler<TypeOfSender, TypeOfEventArguments> CustomEvent;
quelle
Generika und Verlauf würden eine große Rolle spielen, insbesondere bei der Anzahl der Steuerelemente (usw.), die ähnliche Ereignisse offenlegen. Ohne Generika würden Sie am Ende viele Ereignisse aufdecken
Control
, was weitgehend nutzlos ist:object
).Wenn wir Generika in Betracht ziehen, ist wieder alles in Ordnung, aber Sie beginnen dann, Probleme mit der Vererbung zu bekommen. Wenn Klasse
B : A
, sollten dann EreignisseA
seinEventHandler<A, ...>
und EreignisseB
seinEventHandler<B, ...>
? Wieder sehr verwirrend, schwer zu bearbeiten und ein bisschen chaotisch in Bezug auf die Sprache.Bis es eine bessere Option gibt, die all dies abdeckt,
object
funktioniert; Ereignisse finden fast immer in Klasseninstanzen statt, daher gibt es kein Boxen usw. - nur eine Besetzung. Und das Casting ist nicht sehr langsam.quelle
Ich denke, das liegt daran, dass Sie in der Lage sein sollten, so etwas zu tun
void SomethingChanged(object sender, EventArgs e) { EnableControls(); } ... MyRadioButton.Click += SomethingChanged; MyCheckbox.Click += SomethingChanged; ...
Warum machen Sie die sichere Besetzung in Ihrem Code? Wenn Sie wissen, dass Sie die Funktion nur als Ereignishandler für den Repeater verwenden, wissen Sie, dass das Argument immer vom richtigen Typ ist, und Sie können stattdessen einen Wurfcast verwenden, z. B. (Repeater-) Absender anstelle von (Absender als Repeater).
quelle
Kein guter Grund, jetzt gibt es Kovarianz und Kontravarienz. Ich denke, es ist in Ordnung, einen stark typisierten Absender zu verwenden. Siehe Diskussion in dieser Frage
quelle
Konventionen existieren nur, um Konsistenz zu erzwingen.
Sie KÖNNEN Ihre Event-Handler stark eingeben, wenn Sie dies wünschen, fragen sich jedoch, ob dies einen technischen Vorteil bietet?
Sie sollten berücksichtigen, dass Ereignishandler den Absender nicht immer umwandeln müssen. Der größte Teil des Ereignishandlungscodes, den ich in der Praxis gesehen habe, verwendet den Absenderparameter nicht. Es ist da, WENN es gebraucht wird, aber oft nicht.
Ich sehe oft Fälle, in denen verschiedene Ereignisse auf verschiedenen Objekten einen einzigen gemeinsamen Ereignishandler gemeinsam nutzen. Dies funktioniert, da sich dieser Ereignishandler nicht darum kümmert, wer der Absender war.
Wenn diese Delegierten trotz typischer Verwendung von Generika stark typisiert wären, wäre es SEHR schwierig, einen solchen Event-Handler zu teilen. Wenn Sie es stark tippen, gehen Sie davon aus, dass sich die Handler darum kümmern sollten, was der Absender ist, wenn dies nicht die praktische Realität ist.
Ich denke, Sie sollten sich fragen, warum Sie die Event-Handling-Delegierten stark eingeben würden. Würden Sie auf diese Weise wesentliche funktionale Vorteile hinzufügen? Machen Sie die Verwendung "konsistenter"? Oder setzen Sie nur Annahmen und Einschränkungen auf, nur um stark zu tippen?
quelle
Du sagst:
RepeaterItem item = sender as RepeaterItem if (RepeaterItem != null) { /* Do some stuff */ }
Ist es wirklich viel Code?
Ich würde empfehlen, den
sender
Parameter niemals für einen Ereignishandler zu verwenden. Wie Sie bemerkt haben, ist es nicht statisch typisiert. Es ist nicht unbedingt der direkte Absender des Ereignisses, da manchmal ein Ereignis weitergeleitet wird. Daher erhält derselbe Ereignishandler möglicherweise nichtsender
jedes Mal den gleichen Objekttyp, wenn er ausgelöst wird. Es ist eine unnötige Form der impliziten Kopplung.Wenn Sie sich für ein Ereignis anmelden, müssen Sie zu diesem Zeitpunkt wissen, auf welchem Objekt sich das Ereignis befindet, und das interessiert Sie am wahrscheinlichsten:
Alles andere, was für das Ereignis spezifisch ist, sollte im von EventArgs abgeleiteten zweiten Parameter enthalten sein.
Grundsätzlich ist der
sender
Parameter ein bisschen historisches Rauschen, das am besten vermieden wird.Ich habe hier eine ähnliche Frage gestellt.
quelle
Das liegt daran, dass Sie nie sicher sein können, wer das Ereignis ausgelöst hat. Es gibt keine Möglichkeit einzuschränken, welche Typen ein bestimmtes Ereignis auslösen dürfen.
quelle
Das Muster der Verwendung von EventHandler (Objektabsender, EventArgs e) soll für alle Ereignisse die Möglichkeit bieten, die Ereignisquelle (Absender) zu identifizieren und einen Container für alle spezifischen Nutzdaten des Ereignisses bereitzustellen. Der Vorteil dieses Musters besteht auch darin, dass mit demselben Delegatentyp eine Reihe verschiedener Ereignisse generiert werden können.
Was die Argumente dieses Standarddelegierten betrifft ... Der Vorteil einer einzigen Tasche für den gesamten Status, den Sie zusammen mit dem Ereignis übergeben möchten, liegt auf der Hand, insbesondere wenn sich in diesem Status viele Elemente befinden. Die Verwendung eines Objekts anstelle eines starken Typs ermöglicht die Weitergabe des Ereignisses, möglicherweise an Assemblys, die keinen Verweis auf Ihren Typ haben (in diesem Fall können Sie argumentieren, dass sie den Absender ohnehin nicht verwenden können, aber das ist eine andere Geschichte - Sie können immer noch die Veranstaltung bekommen).
Nach meiner eigenen Erfahrung stimme ich Stephen Redd zu, sehr oft wird der Absender nicht verwendet. Die einzigen Fälle, in denen ich den Absender identifizieren musste, waren UI-Handler, bei denen viele Steuerelemente denselben Event-Handler verwenden (um das Duplizieren von Code zu vermeiden). Ich weiche jedoch von seiner Position ab, da ich kein Problem darin sehe, stark typisierte Delegaten zu definieren und Ereignisse mit stark typisierten Signaturen zu generieren, falls ich weiß, dass es dem Handler egal ist, wer der Absender ist (in der Tat sollte dies häufig nicht der Fall sein) Ich möchte nicht die Unannehmlichkeit haben, den Status in eine Tasche (EventArg-Unterklasse oder generisch) zu stopfen und sie auszupacken. Wenn ich nur 1 oder 2 Elemente in meinem Status habe, kann ich diese Signatur in Ordnung generieren. Für mich ist es eine Frage der Bequemlichkeit: Starkes Tippen bedeutet, dass der Compiler mich auf Trab hält.
Foo foo = sender as Foo; if (foo !=null) { ... }
was den Code besser aussehen lässt :)
Dies ist nur meine Meinung. Ich bin oft von dem empfohlenen Muster für Ereignisse abgewichen und habe keine dafür erlitten. Es ist wichtig, immer klar zu machen, warum es in Ordnung ist, davon abzuweichen. Gute Frage! .
quelle
Das ist eine gute Frage. Ich denke, weil jeder andere Typ Ihren Delegaten verwenden könnte, um ein Ereignis zu deklarieren, können Sie nicht sicher sein, ob der Typ des Absenders wirklich "MyType" ist.
quelle
Ich neige dazu, für jedes Ereignis einen bestimmten Delegatentyp (oder eine kleine Gruppe ähnlicher Ereignisse) zu verwenden. Der nutzlose Absender und Eventargs überladen einfach die API und lenken von den tatsächlich relevanten Informationen ab. Es ist noch nicht sinnvoll, Ereignisse klassenübergreifend "weiterleiten" zu können. Wenn Sie solche Ereignisse an einen Ereignishandler weiterleiten, der einen anderen Ereignistyp darstellt, müssen Sie das Ereignis einschließen sich selbst und die entsprechenden Parameter anzugeben ist wenig Aufwand. Außerdem hat der Spediteur tendenziell eine bessere Vorstellung davon, wie die Ereignisparameter "konvertiert" werden sollen als der endgültige Empfänger.
Kurz gesagt, wenn es keinen dringenden Interop-Grund gibt, geben Sie die nutzlosen, verwirrenden Parameter aus.
quelle