Eine Ereignisdeklaration fügt der Delegateninstanz eine Ebene der Abstraktion und des Schutzes hinzu . Dieser Schutz verhindert, dass Clients des Delegaten den Delegaten und seine Aufrufliste zurücksetzen, und ermöglicht nur das Hinzufügen oder Entfernen von Zielen zur Aufrufliste.
Natürlich verhindert diese Schutzschicht auch, dass "Clients" (Code außerhalb der definierenden Klasse / Struktur) den Delegaten aufrufen und das Delegatenobjekt "hinter" dem Ereignis in irgendeiner Weise erhalten.
Jeppe Stig Nielsen
7
Nicht ganz richtig. Sie können ein Ereignis ohne eine Backend-Delegateninstanz deklarieren. In c # können Sie ein Ereignis explizit implementieren und eine andere Backend-Datenstruktur Ihrer Wahl verwenden.
Miguel Gamboa
3
@mmcdole kannst du ein Beispiel geben, um seine zu erklären?
Vivek Nuna
102
Um die Unterschiede zu verstehen, können Sie sich diese beiden Beispiele ansehen
Beispiel mit Delegaten (in diesem Fall eine Aktion - das ist eine Art Delegat, der keinen Wert zurückgibt)
Um den Delegaten zu verwenden, sollten Sie Folgendes tun:
Animal animal=newAnimal();
animal.Run+=()=>Console.WriteLine("I'm running");
animal.Run+=()=>Console.WriteLine("I'm still running");
animal.RaiseEvent();
Dieser Code funktioniert gut, aber Sie könnten einige Schwachstellen haben.
Zum Beispiel, wenn ich das schreibe:
animal.Run+=()=>Console.WriteLine("I'm running");
animal.Run+=()=>Console.WriteLine("I'm still running");
animal.Run=()=>Console.WriteLine("I'm sleeping");
Mit der letzten Codezeile habe ich die vorherigen Verhaltensweisen nur mit einem fehlenden überschrieben +(ich habe =statt verwendet +=)
Eine weitere Schwachstelle ist, dass jede Klasse, die Ihre AnimalKlasse verwendet, RaiseEventnur einen Aufruf auslösen kann animal.RaiseEvent().
Um diese Schwachstellen zu vermeiden, können Sie eventsin c # verwenden.
Ihre Tierklasse ändert sich folgendermaßen:
publicclassArgsSpecial:EventArgs{publicArgsSpecial(string val){Operation=val;}publicstringOperation{get;set;}}publicclassAnimal{// Empty delegate. In this way you are sure that value is always != null // because no one outside of the class can change it.publiceventEventHandler<ArgsSpecial>Run=delegate{}publicvoidRaiseEvent(){Run(this,newArgsSpecial("Run faster"));}}
Ereignisse aufrufen
Animal animal=newAnimal();
animal.Run+=(sender, e)=>Console.WriteLine("I'm running. My value is {0}", e.Operation);
animal.RaiseEvent();
Unterschiede:
Sie verwenden keine öffentliche Eigenschaft, sondern ein öffentliches Feld (mithilfe von Ereignissen schützt der Compiler Ihre Felder vor unerwünschtem Zugriff).
Ereignisse können nicht direkt zugewiesen werden. In diesem Fall wird der vorherige Fehler, den ich beim Überschreiben des Verhaltens gezeigt habe, nicht verursacht.
Niemand außerhalb Ihrer Klasse kann das Ereignis auslösen.
Ereignisse können in eine Schnittstellendeklaration aufgenommen werden, ein Feld jedoch nicht
Anmerkungen:
EventHandler wird als folgender Delegat deklariert:
Alles sah großartig aus, bis ich auf "Niemand außerhalb Ihrer Klasse kann das Ereignis auslösen" stieß. Was bedeutet das? Kann niemand aufrufen RaiseEvent, solange eine aufrufende Methode Zugriff auf eine Instanz des animalCodes hat, der das Ereignis verwendet?
Tanz2die
11
@Sung Events können nur innerhalb der Klasse ausgelöst werden, vielleicht habe ich das nicht klar erklärt. Bei Ereignissen können Sie die Funktion aufrufen, die das Ereignis auslöst (Kapselung), sie kann jedoch nur innerhalb der Klasse ausgelöst werden, die es definiert. Lassen Sie mich wissen, wenn ich nicht klar bin.
Faby
1
"Ereignisse können nicht direkt zugewiesen werden." Wenn ich dich nicht falsch verstehe, ist das nicht wahr. Hier ist ein Beispiel: gist.github.com/Chiel92/36bb3a2d2ac7dd511b96
Chiel ten Brinke
2
@faby, du meinst, obwohl das Ereignis als öffentlich deklariert ist, kann ich es immer noch nicht tun animal.Run(this, new ArgsSpecial("Run faster");?
Pap
1
@ChaltenBrinke Natürlich kann das Ereignis innerhalb von Mitgliedern der Klasse zugewiesen werden ... aber nicht anders.
Jim Balter
93
Neben den syntaktischen und operativen Eigenschaften gibt es auch einen semantischen Unterschied.
Delegierte sind konzeptionell Funktionsvorlagen. Das heißt, sie drücken einen Vertrag aus, den eine Funktion einhalten muss, um vom "Typ" des Delegierten zu sein.
Ereignisse repräsentieren ... nun, Ereignisse. Sie sollen jemanden alarmieren, wenn etwas passiert, und ja, sie halten sich an eine Delegiertendefinition, aber sie sind nicht dasselbe.
Selbst wenn sie genau dasselbe wären (syntaktisch und im IL-Code), bleibt der semantische Unterschied bestehen. Im Allgemeinen bevorzuge ich zwei unterschiedliche Namen für zwei unterschiedliche Konzepte, auch wenn sie auf dieselbe Weise implementiert sind (was nicht bedeutet, dass ich denselben Code zweimal haben möchte).
Können wir also sagen, dass eine Veranstaltung ein "besonderer" Typ eines Delegierten ist?
Pap
Ich verstehe Ihren Standpunkt nicht. Sie können einen Delegierten verwenden, um "jemanden zu benachrichtigen, wenn etwas passiert". Vielleicht würden Sie das nicht tun, aber Sie können und deshalb ist es keine inhärente Eigenschaft des Ereignisses.
Steve
@Jorge Córdoba Beispiel für einen Delegierten und einen Delegierten für Veranstaltungen ist ein Zeitungsinhaber und für Veranstaltungen (Abonnieren oder Abbestellen). Einige Leute kaufen die Zeitung, andere kaufen die Zeitung nicht richtig oder falsch?
Kurz gesagt, das Herausnehmen aus dem Artikel - Ereignisse sind Kapselung über Delegierte.
Zitat aus dem Artikel:
Angenommen, Ereignisse waren in C # /. NET nicht als Konzept vorhanden. Wie würde eine andere Klasse eine Veranstaltung abonnieren? Drei Optionen:
Eine öffentliche Delegatenvariable
Eine Delegatenvariable, die von einer Eigenschaft unterstützt wird
Eine Delegatvariable mit den Methoden AddXXXHandler und RemoveXXXHandler
Option 1 ist eindeutig schrecklich, aus all den normalen Gründen verabscheuen wir öffentliche Variablen.
Option 2 ist etwas besser, ermöglicht es den Abonnenten jedoch, sich gegenseitig effektiv zu überschreiben. Es wäre allzu einfach, someInstance.MyEvent = eventHandler zu schreiben. Dies würde alle vorhandenen Ereignishandler ersetzen, anstatt einen neuen hinzuzufügen. Außerdem müssen Sie die Eigenschaften noch schreiben.
Option 3 ist im Grunde das, was Ereignisse Ihnen bieten, aber mit einer garantierten Konvention (vom Compiler generiert und durch zusätzliche Flags in der IL unterstützt) und einer "kostenlosen" Implementierung, wenn Sie mit der Semantik zufrieden sind, die feldähnliche Ereignisse Ihnen bieten. Das Abonnieren und Abbestellen von Ereignissen ist gekapselt, ohne dass ein willkürlicher Zugriff auf die Liste der Ereignishandler möglich ist. Sprachen können die Arbeit vereinfachen, indem sie die Syntax für Deklaration und Abonnement bereitstellen.
Dies ist eher ein theoretisches Problem als alles andere, aber FWIW Ich hatte immer das Gefühl, dass das Argument "Option 1 ist schlecht, weil wir keine öffentlichen Variablen mögen" etwas mehr Klarheit gebrauchen könnte. Wenn er das sagt, weil es "schlechte OOP-Praxis" ist, würde eine Variable technisch gesehenpublic Delegate "Daten" offenlegen, aber nach meinem besten Wissen hat OOP niemals Konzepte wie ein erwähnt Delegate(es ist weder ein "Objekt" noch eine "Nachricht"). und .NET behandelt Delegierte sowieso kaum wie Daten.
jrh
Wenn Sie sich in einer Situation befinden, in der Sie sicherstellen möchten, dass es nur einen Handler gibt, kann es jedoch eine gute Option sein, eigene AddXXXHandlerMethoden mit einer private DelegateVariablen zu erstellen. In diesem Fall können Sie überprüfen, ob bereits ein Handler eingerichtet ist, und entsprechend reagieren. Dies kann auch eine gute Einrichtung sein, wenn Sie das Objekt benötigen, das das enthält Delegate, um alle Handler löschen zu können ( eventgibt Ihnen keine Möglichkeit, dies zu tun).
jrh
7
HINWEIS: Wenn Sie Zugriff auf C # 5.0 Unleashed haben , lesen Sie die "Einschränkungen für die einfache Verwendung von Delegaten" in Kapitel 18 mit dem Titel "Ereignisse", um die Unterschiede zwischen den beiden besser zu verstehen.
Es hilft mir immer, ein einfaches, konkretes Beispiel zu haben. Also hier ist eine für die Community. Zuerst zeige ich, wie Sie Delegierte alleine verwenden können, um das zu tun, was Events für uns tun. Dann zeige ich, wie dieselbe Lösung mit einer Instanz von funktionieren würde EventHandler. Und dann erkläre ich, warum wir NICHT das tun wollen, was ich im ersten Beispiel erkläre. Dieser Beitrag wurde von einem Artikel von John Skeet inspiriert .
Beispiel 1: Verwenden eines öffentlichen Delegaten
Angenommen, ich habe eine WinForms-App mit einer einzelnen Dropdown-Box. Das Dropdown ist an ein gebunden List<Person>. Wobei Person Eigenschaften von ID, Name, Spitzname, Haarfarbe hat. Auf dem Hauptformular befindet sich ein benutzerdefiniertes Benutzersteuerelement, das die Eigenschaften dieser Person anzeigt. Wenn jemand eine Person in der Dropdown-Liste auswählt, werden die Beschriftungen im Benutzersteuerelement aktualisiert, um die Eigenschaften der ausgewählten Person anzuzeigen.
So funktioniert das Wir haben drei Dateien, die uns dabei helfen, dies zusammenzustellen:
Mediator.cs - statische Klasse enthält die Delegaten
Form1.cs - Hauptformular
DetailView.cs - Benutzersteuerung zeigt alle Details
Hier ist der relevante Code für jede der Klassen:
classMediator{publicdelegatevoidPersonChangedDelegate(Person p);//delegate type definitionpublicstaticPersonChangedDelegatePersonChangedDel;//delegate instance. Detail view will "subscribe" to this.publicstaticvoidOnPersonChanged(Person p)//Form1 will call this when the drop-down changes.{if(PersonChangedDel!=null){PersonChangedDel(p);}}}
Schließlich haben wir den folgenden Code in unserer Form1.cs. Hier rufen wir OnPersonChanged auf, das jeden Code aufruft, der den Delegaten abonniert hat.
privatevoid comboBox1_SelectedIndexChanged(object sender,EventArgs e){Mediator.OnPersonChanged((Person)comboBox1.SelectedItem);//Call the mediator's OnPersonChanged method. This will in turn call all the methods assigned (i.e. subscribed to) to the delegate -- in this case `DetailView_PersonChanged`.}
OK. So würden Sie dies zum Laufen bringen, ohne Ereignisse und nur Delegierte zu verwenden . Wir haben nur einen öffentlichen Delegierten in eine Klasse eingefügt - Sie können ihn statisch oder als Singleton oder was auch immer erstellen. Großartig.
ABER, ABER, ABER wir wollen nicht das tun, was ich gerade oben beschrieben habe. Weil öffentliche Felder aus vielen, vielen Gründen schlecht sind . Welche Möglichkeiten haben wir? Wie John Skeet beschreibt, sind hier unsere Optionen:
Eine öffentliche Delegatenvariable (das haben wir gerade oben gemacht. Tu das nicht. Ich habe dir oben nur gesagt, warum es schlecht ist.)
Fügen Sie den Delegaten in eine Eigenschaft mit einem get / set ein (Problem hierbei ist, dass Abonnenten sich gegenseitig überschreiben können - wir könnten also eine Reihe von Methoden für den Delegaten abonnieren und dann versehentlich sagen PersonChangedDel = null, dass alle anderen Abonnements gelöscht werden Ein weiteres Problem besteht darin, dass die Benutzer, da sie Zugriff auf den Delegaten haben, die Ziele in der Aufrufliste aufrufen können. Wir möchten nicht, dass externe Benutzer Zugriff darauf haben, wann unsere Ereignisse ausgelöst werden.
Eine Delegatvariable mit den Methoden AddXXXHandler und RemoveXXXHandler
Diese dritte Option ist im Wesentlichen das, was uns ein Ereignis bietet. Wenn wir einen EventHandler deklarieren, erhalten wir Zugriff auf einen Delegaten - nicht öffentlich, nicht als Eigenschaft, sondern als Ereignis, das nur Accessors hinzufügt / entfernt.
Mal sehen, wie das gleiche Programm aussieht, aber jetzt ein Ereignis anstelle des öffentlichen Delegaten verwendet (ich habe auch unseren Mediator in einen Singleton geändert):
Beispiel 2: Mit EventHandler anstelle eines öffentlichen Delegaten
Vermittler:
classMediator{privatestaticreadonlyMediator_Instance=newMediator();privateMediator(){}publicstaticMediatorGetInstance(){return_Instance;}publiceventEventHandler<PersonChangedEventArgs>PersonChanged;//this is just a property we expose to add items to the delegate.publicvoidOnPersonChanged(object sender,Person p){var personChangedDelegate =PersonChangedasEventHandler<PersonChangedEventArgs>;if(personChangedDelegate !=null){
personChangedDelegate(sender,newPersonChangedEventArgs(){Person= p });}}}
Beachten Sie, dass wenn Sie F12 im EventHandler verwenden, angezeigt wird, dass es sich bei der Definition nur um einen generischen Delegierten mit dem zusätzlichen Objekt "Absender" handelt:
Obwohl ich die gute Arbeit in diesem Beitrag sehr schätze und das meiste davon gerne gelesen habe, habe ich immer noch das Gefühl, dass ein Problem nicht angesprochen wird - The other problem that remains here is that since the users have access to the delegate, they can invoke the targets in the invocation list -- we don't want external users having access to when to raise our events. In der neuesten Version von Mediatorkönnen Sie immer noch aufrufen, OnPersonChangewenn Sie einen Verweis auf den Singleton haben. Vielleicht sollten Sie erwähnen, dass der MediatorAnsatz dieses bestimmte Verhalten nicht verhindert und näher an einem Ereignisbus liegt.
Ivaylo Slavov
6
Sie können Ereignisse auch in Schnittstellendeklarationen verwenden, nicht jedoch für Delegaten.
@surfen Interface kann Ereignisse enthalten, aber keine Delegaten.
Alexandr Nikitin
1
Was genau meinst du? Sie können Action a { get; set; }innerhalb einer Schnittstelle Definition haben.
Chiel ten Brinke
6
Was für ein großes Missverständnis zwischen Veranstaltungen und Delegierten !!! Ein Delegat gibt einen TYP an (z. B. a classoder interfacedo), während ein Ereignis nur eine Art MITGLIED ist (z. B. Felder, Eigenschaften usw.). Und genau wie jedes andere Mitglied hat auch eine Veranstaltung einen Typ. Im Falle eines Ereignisses muss der Typ des Ereignisses jedoch von einem Delegierten angegeben werden. Beispielsweise können Sie ein Ereignis eines von einer Schnittstelle definierten Typs NICHT deklarieren.
Abschließend können wir folgende Bemerkung machen: Der Typ eines Ereignisses MUSS von einem Delegierten definiert werden . Dies ist die Hauptbeziehung zwischen einem Ereignis und einem Delegierten und wird in Abschnitt II.18 Definieren von Ereignissen der ECMA-335 (CLI) -Partitionen I bis VI beschrieben :
In der typischen Verwendung identifiziert die TypeSpec (falls vorhanden) einen Delegaten, dessen Signatur mit den Argumenten übereinstimmt, die an die Feuermethode des Ereignisses übergeben wurden.
Diese Tatsache bedeutet jedoch NICHT, dass ein Ereignis ein Hintergrunddelegatenfeld verwendet . In Wahrheit kann ein Ereignis ein Hintergrundfeld eines anderen Datenstrukturtyps Ihrer Wahl verwenden. Wenn Sie ein Ereignis explizit in C # implementieren, können Sie frei wählen, wie Sie die Ereignishandler speichern (beachten Sie, dass Ereignishandler Instanzen des Ereignistyps sind , der wiederum zwingend ein Delegatentyp ist - aus der vorherigen Beobachtung ). Sie können diese Ereignishandler (die delegierte Instanzen sind) jedoch in einer Datenstruktur wie einer Listoder einer Dictionaryanderen oder sogar in einem Hintergrunddelegiertenfeld speichern . Vergessen Sie jedoch nicht, dass Sie KEIN Delegatenfeld verwenden müssen.
Ein Ereignis in .net ist eine bestimmte Kombination aus einer Add-Methode und einer Remove-Methode, die beide einen bestimmten Delegatentyp erwarten. Sowohl C # als auch vb.net können automatisch Code für die Methoden zum Hinzufügen und Entfernen generieren, die einen Delegaten definieren, der die Ereignisabonnements enthält, und den übergebenen Delegaten zu / von diesem Abonnementdelegierten hinzufügen / entfernen. VB.net generiert außerdem automatisch Code (mit der RaiseEvent-Anweisung), um die Abonnementliste genau dann aufzurufen, wenn sie nicht leer ist. Aus irgendeinem Grund generiert C # letzteres nicht.
Beachten Sie, dass es zwar üblich ist, Ereignisabonnements mit einem Multicast-Delegaten zu verwalten, dies jedoch nicht das einzige Mittel ist. Aus öffentlicher Sicht muss ein potenzieller Ereignisabonnent wissen, wie er ein Objekt wissen lässt, dass es Ereignisse empfangen möchte, aber er muss nicht wissen, welchen Mechanismus der Herausgeber zum Auslösen der Ereignisse verwendet. Beachten Sie auch, dass, wer auch immer die Ereignisdatenstruktur in .net definiert hat, anscheinend der Meinung war, dass es ein öffentliches Mittel zum Auslösen geben sollte, weder C # noch vb.net diese Funktion nutzen.
So definieren Sie ein Ereignis auf einfache Weise:
Ereignis ist eine VERWEIS auf einen Delegierten mit zwei Einschränkungen
Kann nicht direkt aufgerufen werden
Werte können nicht direkt zugewiesen werden (z. B. eventObj = delegateMethod)
Über zwei sind die Schwachstellen für die Delegierten und es wird im Ereignis angesprochen. Ein vollständiges Codebeispiel, um den Unterschied zwischen Geigern zu zeigen, finden Sie hier https://dotnetfiddle.net/5iR3fB .
Schalten Sie den Kommentar zwischen Ereignis und Delegat und Client-Code um, der zu delegierende Werte aufruft / zuweist, um den Unterschied zu verstehen
Hier ist der Inline-Code.
/*
This is working program in Visual Studio. It is not running in fiddler because of infinite loop in code.
This code demonstrates the difference between event and delegate
Event is an delegate reference with two restrictions for increased protection
1. Cannot be invoked directly
2. Cannot assign value to delegate reference directly
Toggle between Event vs Delegate in the code by commenting/un commenting the relevant lines
*/publicclassRoomTemperatureController{privateint _roomTemperature =25;//Default/Starting room Temperatureprivatebool _isAirConditionTurnedOn =false;//Default AC is Offprivatebool _isHeatTurnedOn =false;//Default Heat is Offprivatebool _tempSimulator =false;publicdelegatevoidOnRoomTemperatureChange(int roomTemperature);//OnRoomTemperatureChange is a type of Delegate (Check next line for proof)// public OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), publiceventOnRoomTemperatureChangeWhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), publicRoomTemperatureController(){WhenRoomTemperatureChange+=InternalRoomTemperatuerHandler;}privatevoidInternalRoomTemperatuerHandler(int roomTemp){System.Console.WriteLine("Internal Room Temperature Handler - Mandatory to handle/ Should not be removed by external consumer of ths class: Note, if it is delegate this can be removed, if event cannot be removed");}//User cannot directly asign values to delegate (e.g. roomTempControllerObj.OnRoomTemperatureChange = delegateMethod (System will throw error)publicboolTurnRoomTeperatureSimulator{set{
_tempSimulator =value;if(value){SimulateRoomTemperature();//Turn on Simulator }}get{return _tempSimulator;}}publicvoidTurnAirCondition(bool val){
_isAirConditionTurnedOn = val;
_isHeatTurnedOn =!val;//Binary switch If Heat is ON - AC will turned off automatically (binary)System.Console.WriteLine("Aircondition :"+ _isAirConditionTurnedOn);System.Console.WriteLine("Heat :"+ _isHeatTurnedOn);}publicvoidTurnHeat(bool val){
_isHeatTurnedOn = val;
_isAirConditionTurnedOn =!val;//Binary switch If Heat is ON - AC will turned off automatically (binary)System.Console.WriteLine("Aircondition :"+ _isAirConditionTurnedOn);System.Console.WriteLine("Heat :"+ _isHeatTurnedOn);}publicasyncvoidSimulateRoomTemperature(){while(_tempSimulator){if(_isAirConditionTurnedOn)
_roomTemperature--;//Decrease Room Temperature if AC is turned Onif(_isHeatTurnedOn)
_roomTemperature++;//Decrease Room Temperature if AC is turned OnSystem.Console.WriteLine("Temperature :"+ _roomTemperature);if(WhenRoomTemperatureChange!=null)WhenRoomTemperatureChange(_roomTemperature);System.Threading.Thread.Sleep(500);//Every second Temperature changes based on AC/Heat Status}}}publicclassMySweetHome{RoomTemperatureController roomController =null;publicMySweetHome(){
roomController =newRoomTemperatureController();
roomController.WhenRoomTemperatureChange+=TurnHeatOrACBasedOnTemp;//roomController.WhenRoomTemperatureChange = null; //Setting NULL to delegate reference is possible where as for Event it is not possible.//roomController.WhenRoomTemperatureChange.DynamicInvoke();//Dynamic Invoke is possible for Delgate and not possible with Event
roomController.SimulateRoomTemperature();System.Threading.Thread.Sleep(5000);
roomController.TurnAirCondition(true);
roomController.TurnRoomTeperatureSimulator=true;}publicvoidTurnHeatOrACBasedOnTemp(int temp){if(temp >=30)
roomController.TurnAirCondition(true);if(temp <=15)
roomController.TurnHeat(true);}publicstaticvoidMain(string[]args){MySweetHome home =newMySweetHome();}}
Delegate ist ein typsicherer Funktionszeiger. Event ist eine Implementierung des Publisher-Subscriber-Entwurfsmusters unter Verwendung eines Delegaten.
Wenn Sie Intermediate Language aktivieren, wissen Sie, dass der .net-Compiler den Delegaten in eine versiegelte Klasse in IL konvertiert, mit einigen integrierten Funktionen wie invoke, beginInvoke, endInvoke und delegate class, die von einer anderen Klasse geerbt wurden und möglicherweise als "SystemMulticast" bezeichnet werden. Ich denke, Event ist eine untergeordnete Klasse von Delegaten mit einigen zusätzlichen Eigenschaften.
Der Unterschied zwischen Ereignisinstanz und Delegat besteht darin, dass Sie kein Ereignis außerhalb der Deklaration ausführen können. Wenn Sie ein Ereignis in Klasse A deklarieren, können Sie dieses Ereignis nur in Klasse A ausführen. Wenn Sie einen Delegaten in Klasse A deklarieren, können Sie diesen Delegaten überall verwenden. Ich denke, das ist ein Hauptunterschied zwischen ihnen
Antworten:
Eine Ereignisdeklaration fügt der Delegateninstanz eine Ebene der Abstraktion und des Schutzes hinzu . Dieser Schutz verhindert, dass Clients des Delegaten den Delegaten und seine Aufrufliste zurücksetzen, und ermöglicht nur das Hinzufügen oder Entfernen von Zielen zur Aufrufliste.
quelle
Um die Unterschiede zu verstehen, können Sie sich diese beiden Beispiele ansehen
Beispiel mit Delegaten (in diesem Fall eine Aktion - das ist eine Art Delegat, der keinen Wert zurückgibt)
Um den Delegaten zu verwenden, sollten Sie Folgendes tun:
Dieser Code funktioniert gut, aber Sie könnten einige Schwachstellen haben.
Zum Beispiel, wenn ich das schreibe:
Mit der letzten Codezeile habe ich die vorherigen Verhaltensweisen nur mit einem fehlenden überschrieben
+
(ich habe=
statt verwendet+=
)Eine weitere Schwachstelle ist, dass jede Klasse, die Ihre
Animal
Klasse verwendet,RaiseEvent
nur einen Aufruf auslösen kannanimal.RaiseEvent()
.Um diese Schwachstellen zu vermeiden, können Sie
events
in c # verwenden.Ihre Tierklasse ändert sich folgendermaßen:
Ereignisse aufrufen
Unterschiede:
Anmerkungen:
EventHandler wird als folgender Delegat deklariert:
Es werden ein Absender (vom Objekttyp) und Ereignisargumente benötigt. Der Absender ist null, wenn er aus statischen Methoden stammt.
Dieses Beispiel, das verwendet
EventHandler<ArgsSpecial>
, kannEventHandler
stattdessen auch mit geschrieben werden .Siehe hier für die Dokumentation über Eventhandler
quelle
RaiseEvent
, solange eine aufrufende Methode Zugriff auf eine Instanz desanimal
Codes hat, der das Ereignis verwendet?animal.Run(this, new ArgsSpecial("Run faster");
?Neben den syntaktischen und operativen Eigenschaften gibt es auch einen semantischen Unterschied.
Delegierte sind konzeptionell Funktionsvorlagen. Das heißt, sie drücken einen Vertrag aus, den eine Funktion einhalten muss, um vom "Typ" des Delegierten zu sein.
Ereignisse repräsentieren ... nun, Ereignisse. Sie sollen jemanden alarmieren, wenn etwas passiert, und ja, sie halten sich an eine Delegiertendefinition, aber sie sind nicht dasselbe.
Selbst wenn sie genau dasselbe wären (syntaktisch und im IL-Code), bleibt der semantische Unterschied bestehen. Im Allgemeinen bevorzuge ich zwei unterschiedliche Namen für zwei unterschiedliche Konzepte, auch wenn sie auf dieselbe Weise implementiert sind (was nicht bedeutet, dass ich denselben Code zweimal haben möchte).
quelle
Hier ist ein weiterer guter Link, auf den Sie verweisen können. http://csharpindepth.com/Articles/Chapter2/Events.aspx
Kurz gesagt, das Herausnehmen aus dem Artikel - Ereignisse sind Kapselung über Delegierte.
Zitat aus dem Artikel:
quelle
public Delegate
"Daten" offenlegen, aber nach meinem besten Wissen hat OOP niemals Konzepte wie ein erwähntDelegate
(es ist weder ein "Objekt" noch eine "Nachricht"). und .NET behandelt Delegierte sowieso kaum wie Daten.AddXXXHandler
Methoden mit einerprivate Delegate
Variablen zu erstellen. In diesem Fall können Sie überprüfen, ob bereits ein Handler eingerichtet ist, und entsprechend reagieren. Dies kann auch eine gute Einrichtung sein, wenn Sie das Objekt benötigen, das das enthältDelegate
, um alle Handler löschen zu können (event
gibt Ihnen keine Möglichkeit, dies zu tun).HINWEIS: Wenn Sie Zugriff auf C # 5.0 Unleashed haben , lesen Sie die "Einschränkungen für die einfache Verwendung von Delegaten" in Kapitel 18 mit dem Titel "Ereignisse", um die Unterschiede zwischen den beiden besser zu verstehen.
Es hilft mir immer, ein einfaches, konkretes Beispiel zu haben. Also hier ist eine für die Community. Zuerst zeige ich, wie Sie Delegierte alleine verwenden können, um das zu tun, was Events für uns tun. Dann zeige ich, wie dieselbe Lösung mit einer Instanz von funktionieren würde
EventHandler
. Und dann erkläre ich, warum wir NICHT das tun wollen, was ich im ersten Beispiel erkläre. Dieser Beitrag wurde von einem Artikel von John Skeet inspiriert .Beispiel 1: Verwenden eines öffentlichen Delegaten
Angenommen, ich habe eine WinForms-App mit einer einzelnen Dropdown-Box. Das Dropdown ist an ein gebunden
List<Person>
. Wobei Person Eigenschaften von ID, Name, Spitzname, Haarfarbe hat. Auf dem Hauptformular befindet sich ein benutzerdefiniertes Benutzersteuerelement, das die Eigenschaften dieser Person anzeigt. Wenn jemand eine Person in der Dropdown-Liste auswählt, werden die Beschriftungen im Benutzersteuerelement aktualisiert, um die Eigenschaften der ausgewählten Person anzuzeigen.So funktioniert das Wir haben drei Dateien, die uns dabei helfen, dies zusammenzustellen:
Hier ist der relevante Code für jede der Klassen:
Hier ist unsere Benutzersteuerung:
Schließlich haben wir den folgenden Code in unserer Form1.cs. Hier rufen wir OnPersonChanged auf, das jeden Code aufruft, der den Delegaten abonniert hat.
OK. So würden Sie dies zum Laufen bringen, ohne Ereignisse und nur Delegierte zu verwenden . Wir haben nur einen öffentlichen Delegierten in eine Klasse eingefügt - Sie können ihn statisch oder als Singleton oder was auch immer erstellen. Großartig.
ABER, ABER, ABER wir wollen nicht das tun, was ich gerade oben beschrieben habe. Weil öffentliche Felder aus vielen, vielen Gründen schlecht sind . Welche Möglichkeiten haben wir? Wie John Skeet beschreibt, sind hier unsere Optionen:
PersonChangedDel = null
, dass alle anderen Abonnements gelöscht werden Ein weiteres Problem besteht darin, dass die Benutzer, da sie Zugriff auf den Delegaten haben, die Ziele in der Aufrufliste aufrufen können. Wir möchten nicht, dass externe Benutzer Zugriff darauf haben, wann unsere Ereignisse ausgelöst werden.Diese dritte Option ist im Wesentlichen das, was uns ein Ereignis bietet. Wenn wir einen EventHandler deklarieren, erhalten wir Zugriff auf einen Delegaten - nicht öffentlich, nicht als Eigenschaft, sondern als Ereignis, das nur Accessors hinzufügt / entfernt.
Mal sehen, wie das gleiche Programm aussieht, aber jetzt ein Ereignis anstelle des öffentlichen Delegaten verwendet (ich habe auch unseren Mediator in einen Singleton geändert):
Beispiel 2: Mit EventHandler anstelle eines öffentlichen Delegaten
Vermittler:
Beachten Sie, dass wenn Sie F12 im EventHandler verwenden, angezeigt wird, dass es sich bei der Definition nur um einen generischen Delegierten mit dem zusätzlichen Objekt "Absender" handelt:
Die Benutzersteuerung:
Zum Schluss noch der Form1.cs-Code:
Da der EventHandler und EventArgs als Parameter benötigt, habe ich diese Klasse mit nur einer einzigen Eigenschaft erstellt:
Hoffentlich zeigt Ihnen das ein wenig, warum wir Ereignisse haben und wie sie sich von den Delegierten unterscheiden - aber funktional gleich sind.
quelle
The other problem that remains here is that since the users have access to the delegate, they can invoke the targets in the invocation list -- we don't want external users having access to when to raise our events
. In der neuesten Version vonMediator
können Sie immer noch aufrufen,OnPersonChange
wenn Sie einen Verweis auf den Singleton haben. Vielleicht sollten Sie erwähnen, dass derMediator
Ansatz dieses bestimmte Verhalten nicht verhindert und näher an einem Ereignisbus liegt.Sie können Ereignisse auch in Schnittstellendeklarationen verwenden, nicht jedoch für Delegaten.
quelle
Action a { get; set; }
innerhalb einer Schnittstelle Definition haben.Was für ein großes Missverständnis zwischen Veranstaltungen und Delegierten !!! Ein Delegat gibt einen TYP an (z. B. a
class
oderinterface
do), während ein Ereignis nur eine Art MITGLIED ist (z. B. Felder, Eigenschaften usw.). Und genau wie jedes andere Mitglied hat auch eine Veranstaltung einen Typ. Im Falle eines Ereignisses muss der Typ des Ereignisses jedoch von einem Delegierten angegeben werden. Beispielsweise können Sie ein Ereignis eines von einer Schnittstelle definierten Typs NICHT deklarieren.Abschließend können wir folgende Bemerkung machen: Der Typ eines Ereignisses MUSS von einem Delegierten definiert werden . Dies ist die Hauptbeziehung zwischen einem Ereignis und einem Delegierten und wird in Abschnitt II.18 Definieren von Ereignissen der ECMA-335 (CLI) -Partitionen I bis VI beschrieben :
Diese Tatsache bedeutet jedoch NICHT, dass ein Ereignis ein Hintergrunddelegatenfeld verwendet . In Wahrheit kann ein Ereignis ein Hintergrundfeld eines anderen Datenstrukturtyps Ihrer Wahl verwenden. Wenn Sie ein Ereignis explizit in C # implementieren, können Sie frei wählen, wie Sie die Ereignishandler speichern (beachten Sie, dass Ereignishandler Instanzen des Ereignistyps sind , der wiederum zwingend ein Delegatentyp ist - aus der vorherigen Beobachtung ). Sie können diese Ereignishandler (die delegierte Instanzen sind) jedoch in einer Datenstruktur wie einer
List
oder einerDictionary
anderen oder sogar in einem Hintergrunddelegiertenfeld speichern . Vergessen Sie jedoch nicht, dass Sie KEIN Delegatenfeld verwenden müssen.quelle
Ein Ereignis in .net ist eine bestimmte Kombination aus einer Add-Methode und einer Remove-Methode, die beide einen bestimmten Delegatentyp erwarten. Sowohl C # als auch vb.net können automatisch Code für die Methoden zum Hinzufügen und Entfernen generieren, die einen Delegaten definieren, der die Ereignisabonnements enthält, und den übergebenen Delegaten zu / von diesem Abonnementdelegierten hinzufügen / entfernen. VB.net generiert außerdem automatisch Code (mit der RaiseEvent-Anweisung), um die Abonnementliste genau dann aufzurufen, wenn sie nicht leer ist. Aus irgendeinem Grund generiert C # letzteres nicht.
Beachten Sie, dass es zwar üblich ist, Ereignisabonnements mit einem Multicast-Delegaten zu verwalten, dies jedoch nicht das einzige Mittel ist. Aus öffentlicher Sicht muss ein potenzieller Ereignisabonnent wissen, wie er ein Objekt wissen lässt, dass es Ereignisse empfangen möchte, aber er muss nicht wissen, welchen Mechanismus der Herausgeber zum Auslösen der Ereignisse verwendet. Beachten Sie auch, dass, wer auch immer die Ereignisdatenstruktur in .net definiert hat, anscheinend der Meinung war, dass es ein öffentliches Mittel zum Auslösen geben sollte, weder C # noch vb.net diese Funktion nutzen.
quelle
So definieren Sie ein Ereignis auf einfache Weise:
Ereignis ist eine VERWEIS auf einen Delegierten mit zwei Einschränkungen
Über zwei sind die Schwachstellen für die Delegierten und es wird im Ereignis angesprochen. Ein vollständiges Codebeispiel, um den Unterschied zwischen Geigern zu zeigen, finden Sie hier https://dotnetfiddle.net/5iR3fB .
Schalten Sie den Kommentar zwischen Ereignis und Delegat und Client-Code um, der zu delegierende Werte aufruft / zuweist, um den Unterschied zu verstehen
Hier ist der Inline-Code.
quelle
Delegate ist ein typsicherer Funktionszeiger. Event ist eine Implementierung des Publisher-Subscriber-Entwurfsmusters unter Verwendung eines Delegaten.
quelle
Wenn Sie Intermediate Language aktivieren, wissen Sie, dass der .net-Compiler den Delegaten in eine versiegelte Klasse in IL konvertiert, mit einigen integrierten Funktionen wie invoke, beginInvoke, endInvoke und delegate class, die von einer anderen Klasse geerbt wurden und möglicherweise als "SystemMulticast" bezeichnet werden. Ich denke, Event ist eine untergeordnete Klasse von Delegaten mit einigen zusätzlichen Eigenschaften.
Der Unterschied zwischen Ereignisinstanz und Delegat besteht darin, dass Sie kein Ereignis außerhalb der Deklaration ausführen können. Wenn Sie ein Ereignis in Klasse A deklarieren, können Sie dieses Ereignis nur in Klasse A ausführen. Wenn Sie einen Delegaten in Klasse A deklarieren, können Sie diesen Delegaten überall verwenden. Ich denke, das ist ein Hauptunterschied zwischen ihnen
quelle