Was sind die Unterschiede zwischen Teilnehmern und Ereignissen?

Was sind die Unterschiede zwischen Teilnehmern und Ereignissen? Haben beide nicht Referenzen auf functionen, die ausgeführt werden können?

    Eine Ereignisdeklaration fügt der Delegierteninstanz eine Abstraktions- und Schutzebene hinzu. Dieser Schutz verhindert, dass Clients des Delegaten den Delegaten und seine Aufrufliste zurücksetzen, und erlaubt nur das Hinzufügen oder Entfernen von Zielen aus der Aufrufliste.

    Neben den syntaktischen und operationalen Eigenschaften gibt es auch einen semantischen Unterschied.

    Delegierte sind konzeptionell functionsvorlagen; das heißt, sie drücken einen Vertrag aus, an den sich eine function halten muss, um als “Typ” des Delegierten angesehen zu werden.

    Ereignisse repräsentieren … nun, Ereignisse. Sie sollen jemanden warnen, wenn etwas passiert, und ja, sie halten sich an eine Delegierten-Definition, aber sie sind nicht dasselbe.

    Selbst wenn sie genau dasselbe wären (syntaktisch und im IL-Code), wird immer noch der semantische Unterschied bestehen bleiben. Im Allgemeinen bevorzuge ich zwei unterschiedliche Namen für zwei verschiedene Konzepte, auch wenn sie auf die gleiche Weise implementiert sind (was nicht bedeutet, dass ich den gleichen Code zweimal möchte).

    Um die Unterschiede zu verstehen, können Sie sich diese 2 Beispiele anschauen

    Beispiel mit Delegaten (in diesem Fall eine Aktion – das ist eine Art Delegat, der keinen Wert zurückgibt)

    public class Animal { public Action Run {get; set;} public void RaiseEvent() { if (Run != null) { Run(); } } } 

    Um den Delegaten zu verwenden, sollten Sie Folgendes tun:

     Animal animal= new Animal(); 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 außer Kraft gesetzt mit nur einem fehlenden + (ich habe = anstelle von += )

    Ein weiterer Schwachpunkt ist, dass jede class, die Ihre Animal class verwendet, RaiseEvent nur mit animal.RaiseEvent() aufrufen kann.

    Um diese Schwachstellen zu vermeiden, können Sie events in c # verwenden.

    Ihre Tierklasse wird sich auf diese Weise ändern:

     public class ArgsSpecial : EventArgs { public ArgsSpecial (string val) { Operation=val; } public string Operation {get; set;} } public class Animal { // Empty delegate. In this way you are sure that value is always != null // because no one outside of the class can change it. public event EventHandler Run = delegate{} public void RaiseEvent() { Run(this, new ArgsSpecial("Run faster")); } } 

    Ereignisse aufrufen

      Animal animal= new Animal(); animal.Run += (sender, e) => Console.WriteLine("I'm running. My value is {0}", e.Operation); animal.RaiseEvent(); 

    Unterschiede:

    1. Sie verwenden keine öffentliche Eigenschaft, sondern ein öffentliches Feld (mithilfe von Ereignissen schützt der Compiler Ihre Felder vor unerwünschtem Zugriff.)
    2. Ereignisse können nicht direkt zugewiesen werden. In diesem Fall wird es nicht zu dem vorherigen Fehler führen, den ich beim Überschreiben des Verhaltens gezeigt habe.
    3. Niemand außerhalb Ihrer class kann das Ereignis auslösen.
    4. Ereignisse können in einer Schnittstellendeklaration enthalten sein, ein Feld dagegen nicht

    Anmerkungen:

    EventHandler wird als der folgende Delegat deklariert:

     public delegate void EventHandler (object sender, EventArgs e) 

    Es nimmt einen Absender (des Objekttyps) und Ereignisargumente. Der Absender ist null, wenn er von statischen Methoden stammt.

    Dieses Beispiel, das EventHandler , kann auch mit EventHandler geschrieben werden.

    Siehe Dokumentation für EventHandler

    Es ist ein alter Beitrag, aber wenn jemand darüber stolpert, wie ich es getan habe – hier ist ein weiterer guter Link zu verweisen .. http://csharpindepth.com/Articles/Chapter2/Events.aspx

    Kurz gesagt, der Weg von dem Artikel – Veranstaltungen sind Verkapselung über Delegierte. Zitat aus Artikel –

    “Angenommen, Ereignisse existierten in C # /. NET nicht als Konzept. Wie würde eine andere class ein Ereignis abonnieren?

    Drei Optionen:

    1. öffentliche Delegatvariable

    2. Delegate-Variable, die von einer Eigenschaft unterstützt wird

    3. delegieren Sie die Variable mit den Methoden AddXXXHandler und RemoveXXXHandler

    Option 1 ist offensichtlich schrecklich, denn aus normalen Gründen verabscheuen wir öffentliche Variablen.

    Option 2 ist besser, aber Abonnenten können sich effektiv gegenseitig überschreiben – es wäre einfach zu schreiben someInstance.MyEvent = eventHandler; Das würde alle vorhandenen Event-Handler ersetzen, anstatt einen neuen hinzuzufügen. Darüber hinaus müssen Sie die Eigenschaften noch schreiben.

    Option 3 ist im Grunde, was Ereignisse Ihnen geben, aber mit einer garantierten Konvention (generiert durch den Compiler und unterstützt durch zusätzliche Flags in der IL) und einer “freien” Implementierung, wenn Sie mit der Semantik zufrieden sind, die feldartige Ereignisse Ihnen geben. Das Abonnieren und Abmelden von Ereignissen wird gekapselt, ohne dass ein beliebiger Zugriff auf die Liste der Ereignisbehandlungsroutinen möglich ist. Sprachen können die Syntax vereinfachen, indem sie sowohl für die Deklaration als auch für die Subskription Syntax bereitstellen. ”

    Sie können auch Ereignisse in Schnittstellendeklarationen verwenden, nicht jedoch für Delegaten.

    Hinweis: Wenn Sie Zugriff auf C # 5.0 Unleashed haben , lesen Sie die “Einschränkungen der einfachen 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 einer für die Community. Zuerst zeige ich, wie Sie Delegierte allein dazu verwenden können, um zu tun, was Ereignisse für uns tun. Dann zeige ich, wie die gleiche Lösung mit einer Instanz von 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 des öffentlichen Delegaten

    Angenommen, ich habe eine WinForms-App mit einer einzigen Dropdown-Box. Das Dropdown ist an eine List gebunden. Wo 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.

    Bildbeschreibung hier eingeben

    So funktioniert das. Wir haben drei Dateien, die uns helfen, das zusammen zu stellen:

    • Mediator.cs – statische class enthält die Delegierten
    • Form1.cs – Hauptformular
    • DetailView.cs – Benutzersteuerung zeigt alle Details an

    Hier ist der relevante Code für jede der classn:

     class Mediator { public delegate void PersonChangedDelegate(Person p); //delegate type definition public static PersonChangedDelegate PersonChangedDel; //delegate instance. Detail view will "subscribe" to this. public static void OnPersonChanged(Person p) //Form1 will call this when the drop-down changes. { if (PersonChangedDel != null) { PersonChangedDel(p); } } } 

    Hier ist unsere Benutzerkontrolle:

     public partial class DetailView : UserControl { public DetailView() { InitializeComponent(); Mediator.PersonChangedDel += DetailView_PersonChanged; } void DetailView_PersonChanged(Person p) { BindData(p); } public void BindData(Person p) { lblPersonHairColor.Text = p.HairColor; lblPersonId.Text = p.IdPerson.ToString(); lblPersonName.Text = p.Name; lblPersonNickName.Text = p.NickName; } } 

    Schließlich haben wir den folgenden Code in unserem Form1.cs. Hier rufen wir OnPersonChanged auf, das jeden Code aufruft, der für den Delegaten abonniert wurde.

     private void 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 (ie subscribed to) to the delegate -- in this case `DetailView_PersonChanged`. } 

    OK. So funktioniert das, ohne Ereignisse zu verwenden und Delegaten zu verwenden . Wir fügen einfach einen öffentlichen Delegierten in eine class ein – Sie können es statisch machen oder ein Singleton oder was auch immer. Groß.

    ABER, ABER, ABER, wir wollen nicht das tun, was ich gerade oben beschrieben habe. Weil öffentliche Felder für viele, viele Gründe schlecht sind . Was sind unsere Möglichkeiten? Wie John Skeet beschreibt, hier sind unsere Möglichkeiten:

    1. Eine öffentliche Delegiertenvariable (das ist, was wir gerade oben getan haben. Tu das nicht. Ich habe dir oben gesagt, warum es schlecht ist)
    2. Setzen Sie den Delegaten in eine Eigenschaft mit einem get / set (Problem ist hier, dass Abonnenten sich gegenseitig überschreiben könnten – also könnten wir eine Reihe von Methoden für den Delegaten subskribieren und dann könnten wir versehentlich PersonChangedDel = null sagen und alle anderen PersonChangedDel = null Das andere Problem, das hier verbleibt, ist, dass die Benutzer Zugriff auf den Delegaten haben und die Ziele in der Aufrufliste aufrufen können – wir möchten nicht, dass externe Benutzer Zugriff darauf haben, wann unsere Ereignisse ausgetriggers werden sollen.
    3. Eine Delegatvariable mit den Methoden AddXXXHandler und RemoveXXXHandler

    Diese dritte Option ist im Wesentlichen, was ein Ereignis uns gibt. Wenn wir einen EventHandler deklarieren, erhalten wir Zugriff auf einen Delegaten – nicht öffentlich, nicht als Eigenschaft, sondern als ein Ereignis, bei dem nur Accessoren hinzugefügt / entfernt werden.

    Mal sehen, wie das gleiche Programm aussieht, aber jetzt ein Event anstelle des öffentlichen Delegaten benutzt (ich habe auch unseren Mediator in einen Singleton geändert):

    Beispiel 2: Mit EventHandler anstelle eines öffentlichen Delegaten

    Vermittler:

     class Mediator { private static readonly Mediator _Instance = new Mediator(); private Mediator() { } public static Mediator GetInstance() { return _Instance; } public event EventHandler PersonChanged; //this is just a property we expose to add items to the delegate. public void OnPersonChanged(object sender, Person p) { var personChangedDelegate = PersonChanged as EventHandler; if (personChangedDelegate != null) { personChangedDelegate(sender, new PersonChangedEventArgs() { Person = p }); } } } 

    Beachten Sie, dass wenn Sie F12 auf dem EventHandler zeigen, zeigt es Ihnen die Definition ist nur ein generisch-ified Delegat mit dem zusätzlichen “Absender” -Objekt:

     public delegate void EventHandler(object sender, TEventArgs e); 

    Die Benutzerkontrolle:

     public partial class DetailView : UserControl { public DetailView() { InitializeComponent(); Mediator.GetInstance().PersonChanged += DetailView_PersonChanged; } void DetailView_PersonChanged(object sender, PersonChangedEventArgs e) { BindData(e.Person); } public void BindData(Person p) { lblPersonHairColor.Text = p.HairColor; lblPersonId.Text = p.IdPerson.ToString(); lblPersonName.Text = p.Name; lblPersonNickName.Text = p.NickName; } } 

    Schließlich ist hier der Code Form1.cs:

     private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { Mediator.GetInstance().OnPersonChanged(this, (Person)comboBox1.SelectedItem); } 

    Da der EventHandler und EventArgs ein Parameter ist, habe ich diese class mit nur einer einzigen Eigenschaft erstellt:

     class PersonChangedEventArgs { public Person Person { get; set; } } 

    Hoffentlich zeigt das ein bisschen, warum wir Events haben und wie sie anders sind – aber funktional das gleiche – als Delegierte.

    Was für ein großes Missverständnis zwischen Veranstaltungen und Delegierten !!! Ein Delegat gibt einen TYPE an (z. B. eine class oder eine interface ), während ein Ereignis nur eine Art MEMBER ist (z. B. Felder, Eigenschaften usw.). Und genau wie jedes andere Mitglied hat auch ein Event einen Typ. Im Falle eines Ereignisses muss der Typ des Ereignisses jedoch von einem Delegaten angegeben werden. Beispielsweise können Sie ein Ereignis eines Typs, der von einer Schnittstelle definiert wird, nicht deklarieren.

    Zusammenfassend können wir folgende Beobachtung machen: Der Typ eines Ereignisses MUSS von einem Delegierten definiert werden . Dies ist die Hauptbeziehung zwischen einem Ereignis und einem Delegaten und wird im Abschnitt II.18 Definieren von Ereignissen der ECMA-335 (CLI) -Partitionen I bis VI beschrieben :

    In der typischen Verwendung identifiziert TypeSpec (falls vorhanden) einen Delegaten, dessen Signatur mit den Argumenten übereinstimmt, die an die fire-Methode des Ereignisses übergeben wurden.

    Diese Tatsache impliziert jedoch NICHT, dass ein Ereignis ein unterstützendes Delegatfeld 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 die Art und Weise, wie Sie die Ereignishandler speichern, frei wählen (Beachten Sie, dass Ereignishandler Instanzen des Ereignistyps sind , der wiederum obligatorisch ein Delegattyp aus der vorherigen Beobachtung ist ). Sie können diese Ereignisbehandlungsroutinen (die Delegat-Instanzen sind) jedoch in einer Datenstruktur speichern, z. B. in einer List einem Dictionary oder einem anderen Element oder sogar in einem Delegiertenfeld. Vergessen Sie jedoch nicht, dass Sie nicht zwingend ein Delegiertenfeld verwenden müssen.

    Ein Ereignis in .net ist eine designierte Kombination einer Add-Methode und einer Remove-Methode, die beide einen bestimmten Delegattyp erwarten. Sowohl C # als auch vb.net können automatisch Code für die Methoden add und remove generieren, die einen Delegaten definieren, der die Ereignissubskriptionen speichert, und den übergebenen Delegierten zu diesem Subskriptionsdelegaten hinzufügen / entfernen. VB.net generiert auch automatisch Code (mit der RaiseEvent-statement), um die Subskriptionsliste genau dann aufzurufen, wenn sie nicht leer ist; Aus irgendeinem Grund erzeugt C # das letztere nicht.

    Beachten Sie, dass es zwar üblich ist, Ereignissubskriptionen mithilfe eines Multicastdelegaten zu verwalten, dies jedoch nicht die einzige Möglichkeit ist. Aus öffentlicher Sicht muss ein potenzieller Event-Abonnent wissen, wie er einem Objekt mitteilen kann, dass er Ereignisse empfangen möchte. Es muss jedoch nicht wissen, über welchen Mechanismus der Publisher die Ereignisse auslösen wird. Beachten Sie auch, dass, während die Person, die die Ereignisdatenstruktur in .net definiert hat, offensichtlich dachte, dass es eine öffentliche Möglichkeit geben sollte, sie zu erhöhen, weder C # noch vb.net diese function nutzen.

    Einfach über das Ereignis definieren:

    Ereignis ist ein Verweis auf einen Delegaten mit zwei Einschränkungen

    1. Kann nicht direkt aufgerufen werden
    2. Kann nicht direkt mit Werten belegt werden (zB eventObj = delegateMethod)

    Über zwei sind die Schwachstellen für die Delegierten und es wird im Ereignisfall angesprochen. Das vollständige Codebeispiel, um den Unterschied in fiddler zu zeigen, ist hier https://dotnetfiddle.net/5iR3fB .

    Schalten Sie den Kommentar zwischen Event und Delegate und dem Client-Code um, der Werte zum delegieren 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 */ public class RoomTemperatureController { private int _roomTemperature = 25;//Default/Starting room Temperature private bool _isAirConditionTurnedOn = false;//Default AC is Off private bool _isHeatTurnedOn = false;//Default Heat is Off private bool _tempSimulator = false; public delegate void OnRoomTemperatureChange(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), public event OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above), public RoomTemperatureController() { WhenRoomTemperatureChange += InternalRoomTemperatuerHandler; } private void InternalRoomTemperatuerHandler(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 (eg roomTempControllerObj.OnRoomTemperatureChange = delegateMethod (System will throw error) public bool TurnRoomTeperatureSimulator { set { _tempSimulator = value; if (value) { SimulateRoomTemperature(); //Turn on Simulator } } get { return _tempSimulator; } } public void TurnAirCondition(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); } public void TurnHeat(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); } public async void SimulateRoomTemperature() { while (_tempSimulator) { if (_isAirConditionTurnedOn) _roomTemperature--;//Decrease Room Temperature if AC is turned On if (_isHeatTurnedOn) _roomTemperature++;//Decrease Room Temperature if AC is turned On System.Console.WriteLine("Temperature :" + _roomTemperature); if (WhenRoomTemperatureChange != null) WhenRoomTemperatureChange(_roomTemperature); System.Threading.Thread.Sleep(500);//Every second Temperature changes based on AC/Heat Status } } } public class MySweetHome { RoomTemperatureController roomController = null; public MySweetHome() { roomController = new RoomTemperatureController(); 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; } public void TurnHeatOrACBasedOnTemp(int temp) { if (temp >= 30) roomController.TurnAirCondition(true); if (temp < = 15) roomController.TurnHeat(true); } public static void Main(string []args) { MySweetHome home = new MySweetHome(); } } 

    Covariance und Contravariance bieten den Delegiertenobjekten zusätzliche Flexibilität. Auf der anderen Seite hat eine Veranstaltung keine solchen Konzepte.

    • Covariance können Sie dem Delegat eine Methode zuweisen, bei der der Rückgabetyp der Methode eine class ist, die von der class abgeleitet ist, die den Rückgabetyp des Delegaten angibt.
    • Contravariance können Sie dem Delegat eine Methode zuweisen, bei der der Parametertyp der Methode eine Basisklasse der class ist, die als Parameter des Delegaten angegeben ist.