Was sind die Unterschiede zwischen Delegierten und Veranstaltungen? Enthalten beide nicht Verweise auf Funktionen, die ausgeführt werden können?
Was sind die Unterschiede zwischen Delegierten und Veranstaltungen? Enthalten beide nicht Verweise auf Funktionen, die ausgeführt werden können?
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.
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 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 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 kann animal.RaiseEvent()
.
Um diese Schwachstellen zu vermeiden, können Sie events
in c # verwenden.
Ihre Tierklasse ändert sich folgendermaßen:
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:
Anmerkungen:
EventHandler wird als folgender Delegat deklariert:
public delegate void EventHandler (object sender, EventArgs e)
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>
, kann EventHandler
stattdessen auch mit geschrieben werden .
Siehe hier für die Dokumentation über Eventhandler
RaiseEvent
, solange eine aufrufende Methode Zugriff auf eine Instanz des animal
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).
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:
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.
public 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.
AddXXXHandler
Methoden mit einer private 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ält Delegate
, 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:
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 den Delegaten abonniert hat.
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 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:
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 verwenden, angezeigt wird, dass es sich bei der Definition nur um einen generischen Delegierten mit dem zusätzlichen Objekt "Absender" 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;
}
}
Zum Schluss noch der Form1.cs-Code:
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Mediator.GetInstance().OnPersonChanged(this, (Person)comboBox1.SelectedItem);
}
Da der EventHandler und EventArgs als Parameter benötigt, habe ich diese Klasse mit nur einer einzigen Eigenschaft erstellt:
class PersonChangedEventArgs
{
public Person Person { get; set; }
}
Hoffentlich zeigt Ihnen das ein wenig, warum wir Ereignisse haben und wie sie sich von den Delegierten unterscheiden - aber funktional gleich sind.
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 Mediator
können Sie immer noch aufrufen, OnPersonChange
wenn Sie einen Verweis auf den Singleton haben. Vielleicht sollten Sie erwähnen, dass der Mediator
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.
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
oder interface
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 :
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 List
oder einer Dictionary
anderen 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
Ü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
*/
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();
}
}
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