Frage

Was sind die Unterschiede zwischen Delegierten und Veranstaltungen?Enthalten beide nicht Verweise auf Funktionen, die ausgeführt werden können?

War es hilfreich?

Lösung

Ein Ereignis Die Deklaration fügt eine Ebene der Abstraktion und des Schutzes hinzu delegieren Beispiel.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.

Andere Tipps

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

Delegaten sind konzeptionell Funktionsvorlagen;Das heißt, sie drücken einen Vertrag aus, an den sich eine Funktion halten muss, um als „Typ“ des Delegierten betrachtet zu werden.

Veranstaltungen repräsentieren ...Nun ja, Ereignisse.Sie sollen jemanden warnen, wenn etwas passiert, und ja, sie folgen einer Delegiertendefinition, sind aber 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 die gleiche Weise implementiert sind (was nicht bedeutet, dass ich denselben Code gerne zweimal haben möchte).

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)

public class Animal
{
    public Action Run {get; set;}

    public void RaiseEvent()
    {
        if (Run != null)
        {
            Run();
        }
    }
}

Um den Delegaten zu verwenden, sollten Sie etwa 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.

Wenn ich zum Beispiel 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 überschrieben, nur dass eines fehlte + (Ich habe benutzt = anstatt +=)

Ein weiterer Schwachpunkt ist, dass jede Klasse, die Ihre verwendet Animal Klasse kann sich erhöhen RaiseEvent rufe es einfach an animal.RaiseEvent().

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

Ihre Tierklasse wird sich folgendermaßen ä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<ArgsSpecial> 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 (mittels Ereignisse schützt der Compiler Ihre Felder vor unerwünschtem Zugriff).
  2. Ereignisse können nicht direkt zugeordnet werden.In diesem Fall wird es nicht zu dem vorherigen Fehler kommen, den ich beim Überschreiben des Verhaltens gezeigt habe.
  3. Niemand außerhalb Ihrer Klasse kann das Ereignis ansprechen.
  4. Ereignisse können in einer Schnittstellendeklaration enthalten sein, ein Feld hingegen nicht

Anmerkungen:

EventHandler wird als folgender Delegat deklariert:

public delegate void EventHandler (object sender, EventArgs e)

Es werden ein Absender (vom Typ Objekt) und Ereignisargumente benötigt.Der Absender ist null, wenn er von statischen Methoden stammt.

Dieses Beispiel, das verwendet EventHandler<ArgsSpecial>, kann auch mit geschrieben werden EventHandler stattdessen.

Verweisen Hier für Dokumentation über EventHandler

Hier ist ein weiterer guter Link, auf den Sie verweisen können.http://csharpin Depth.com/Articles/Chapter2/Events.aspx

Kurz gesagt, das Fazit aus dem Artikel: Ereignisse sind Kapselung über Delegierte.

Zitat aus Artikel:

Angenommen, Ereignisse existierten als Konzept in C#/.NET nicht.Wie würde eine andere Klasse eine Veranstaltung abonnieren?Drei Optionen:

  1. Eine öffentliche Delegate-Variable

  2. Eine Delegate-Variable, die durch eine Eigenschaft unterstützt wird

  3. Eine Delegatvariable mit den Methoden AddXXXHandler und RemoveXXXHandler

Option 1 ist aus all den üblichen Gründen, aus denen wir öffentliche Variablen verabscheuen, eindeutig schrecklich.

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

Option 3 ist im Wesentlichen das, was Ihnen Ereignisse bieten, jedoch 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, die Ihnen feldartige Ereignisse bieten, zufrieden sind.Das Abonnieren und Abbestellen von Ereignissen ist gekapselt, ohne dass willkürlicher Zugriff auf die Liste der Ereignishandler möglich ist. Sprachen können die Arbeit vereinfachen, indem sie Syntax sowohl für die Deklaration als auch für das Abonnement bereitstellen.

NOTIZ:Wenn Sie Zugriff darauf haben C# 5.0 entfesselt, Lesen Sie die „Einschränkungen bei der einfachen Verwendung von Delegierten“ 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.Hier ist also eines für die Community.Zuerst zeige ich, wie Sie Delegierte allein nutzen können, um das zu tun, was Events für uns tun.Dann zeige ich, wie die gleiche Lösung mit einer Instanz von funktionieren würde EventHandler.Und dann erkläre ich, warum wir das, was ich im ersten Beispiel erkläre, NICHT tun wollen.Dieser Beitrag wurde inspiriert von ein Artikel von John Skeet.

Beispiel 1:Verwendung eines öffentlichen Delegaten

Angenommen, ich habe eine WinForms-App mit einem einzigen Dropdown-Feld.Das Dropdown-Menü ist an eine gebunden List<Person>.Wobei „Person“ die Eigenschaften „ID“, „Name“, „Spitzname“ und „Haarfarbe“ hat.Auf dem Hauptformular befindet sich ein benutzerdefiniertes Benutzersteuerelement, das die Eigenschaften dieser Person anzeigt.Wenn jemand eine Person im Dropdown-Menü auswählt, werden die Beschriftungen im Benutzersteuerelement aktualisiert, um die Eigenschaften der ausgewählten Person anzuzeigen.

enter image description here

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 an

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

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 Benutzersteuerung:

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 unserer Form1.cs.Hier rufen wir OnPersonChanged auf, das jeden Code aufruft, der für den Delegaten abonniert ist.

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

OK.So würden Sie das also zum Laufen bringen ohne Ereignisse zu verwenden Und Ich verwende nur Delegierte.Wir haben einfach einen öffentlichen Delegaten in eine Klasse eingefügt – Sie können ihn statisch oder als Singleton oder was auch immer machen.Großartig.

ABER, ABER, ABER, wir wollen nicht das tun, was ich oben gerade beschrieben habe.Weil Öffentliche Bereiche sind schlecht aus vielen, vielen Gründen.Was sind also unsere Optionen?Wie John Skeet beschreibt, sind hier unsere Optionen:

  1. Eine öffentliche Delegate-Variable (das haben wir gerade oben gemacht).Tu das nicht.Ich habe dir oben gerade gesagt, warum es schlecht ist)
  2. Fügen Sie den Delegaten mit einem get/set in eine Eigenschaft ein (das Problem besteht darin, dass Abonnenten sich gegenseitig überschreiben könnten – wir könnten also eine Reihe von Methoden für den Delegaten abonnieren und dann versehentlich sagen PersonChangedDel = null, wodurch alle anderen Abonnements gelöscht werden.Das andere weiterhin bestehende 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 sie unsere Ereignisse auslösen sollen.
  3. 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 dieses Ding, das wir ein Ereignis nennen, das nur Accessoren zum Hinzufügen/Entfernen hat.

Mal sehen, wie das gleiche Programm aussieht, aber jetzt ein Event anstelle des öffentlichen Delegaten verwendet (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<PersonChangedEventArgs> 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<PersonChangedEventArgs>;
        if (personChangedDelegate != null)
        {
            personChangedDelegate(sender, new PersonChangedEventArgs() { Person = p });
        }
    }
}

Beachten Sie, dass wenn Sie F12 im EventHandler drücken, Ihnen angezeigt wird, dass es sich bei der Definition nur um einen generisch definierten Delegaten mit dem zusätzlichen „Sender“-Objekt handelt:

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

Die Benutzersteuerung:

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;

    }
}

Abschließend ist hier der Form1.cs-Code:

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

Da der EventHandler EventArgs als Parameter benötigt, habe ich diese Klasse mit nur einer einzigen Eigenschaft darin erstellt:

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

Hoffentlich zeigt Ihnen das ein wenig darüber, warum wir Veranstaltungen haben und wie sie sich als Delegierte unterscheiden – aber funktional gleich sind.

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

Was für ein großes Missverständnis zwischen Veranstaltungen und Delegierten!!!Ein Delegat gibt einen TYP an (z. B. a class, oder ein interface tut), wohingegen ein Ereignis nur eine Art MEMBER ist (z. B. Felder, Eigenschaften usw.).Und wie jede andere Art von Mitglied hat auch eine Veranstaltung einen Typ.Im Falle einer Veranstaltung muss jedoch die Art der Veranstaltung von einem Delegierten festgelegt werden.Sie können beispielsweise kein Ereignis eines Typs deklarieren, der durch eine Schnittstelle definiert ist.

Abschließend können wir Folgendes machen Überwachung:Der Typ eines Ereignisses MUSS von einem Delegierten definiert werden.Dies ist die Hauptbeziehung zwischen einem Ereignis und einem Delegierten und wird im Abschnitt beschrieben II.18 Ereignisse definieren von ECMA-335 (CLI) Partitionen I bis VI:

Bei typischer Verwendung ist die TypeSpec (falls vorhanden) identifiziert einen Delegierten deren Signatur mit den Argumenten übereinstimmt, die an die Feuermethode des Ereignisses übergeben werden.

Jedoch, Diese Tatsache bedeutet NICHT, dass ein Ereignis ein unterstützendes Delegatenfeld verwendet.Tatsächlich kann ein Ereignis ein Hintergrundfeld eines beliebigen anderen Datenstrukturtyps Ihrer Wahl verwenden.Wenn Sie ein Ereignis explizit in C# implementieren, können Sie die Art und Weise, wie Sie es speichern, frei wählen Ereignishandler (beachten Sie, dass Ereignishandler sind Beispiele dafür Art der Veranstaltung, was wiederum zwingend ein ist Delegatentyp---vom vorherigen Überwachung).Sie können diese Ereignishandler (die Delegateninstanzen sind) jedoch in einer Datenstruktur wie einer speichern List oder ein Dictionary oder irgendein anderes, oder sogar in einem unterstützenden Delegatenfeld.Vergessen Sie jedoch nicht, dass die Verwendung eines Delegiertenfelds NICHT zwingend erforderlich ist.

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 Add- und Remove-Methoden generieren, der einen Delegaten definiert, der die Ereignisabonnements hält, und die übergebenen Delegaten zu diesem Abonnementdelegaten 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 mithilfe eines Multicast-Delegaten zu verwalten, dies jedoch nicht die einzige Möglichkeit ist.Aus öffentlicher Sicht muss ein potenzieller Ereignisabonnent wissen, wie er einem Objekt mitteilen kann, dass es Ereignisse empfangen möchte, aber er muss nicht wissen, welchen Mechanismus der Herausgeber verwenden wird, um die Ereignisse auszulösen.Beachten Sie auch, dass, obwohl derjenige, der die Ereignisdatenstruktur in .net definiert hat, offenbar der Meinung war, dass es eine öffentliche Möglichkeit geben sollte, sie auszulösen, weder C# noch vb.net diese Funktion nutzen.

Um ein Ereignis auf einfache Weise zu definieren:

Ereignis ist ein REFERENZ an einen Delegierten mit zwei Einschränkungen

  1. Kann nicht direkt aufgerufen werden
  2. Werte können nicht direkt zugewiesen werden (z. B. eventObj = DelegateMethod)

Die beiden oben genannten sind die Schwachstellen für Delegierte und werden in der Veranstaltung angesprochen.Ein vollständiges Codebeispiel, das den Unterschied in Fiddler zeigt, finden Sie hier https://dotnetfiddle.net/5iR3fB .

Schalten Sie den Kommentar zwischen „Ereignis“ und „Delegieren“ und dem Clientcode 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 (e.g. 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 Sie den Delegate-Objekten zusätzliche Flexibilität.Andererseits gibt es für ein Ereignis keine derartigen Konzepte.

  • Covariance Ermöglicht dem Delegierten eine Methode, bei der der Rückgabetyp der Methode eine Klasse ist, die aus der Klasse abgeleitet ist, die den Rückgabetyp des Delegiertens angibt.
  • Contravariance Ermöglicht dem Delegierten eine Methode, bei der der Parametertyp der Methode eine Basisklasse der Klasse ist, die als Parameter des Delegiertens angegeben ist.

Delegate ist ein typsicherer Funktionszeiger.Event ist eine Implementierung des Publisher-Subscriber-Entwurfsmusters mithilfe von Delegate.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top