Unterschied zwischen Veranstaltungen und Delegierten und ihren jeweiligen Anwendungen [geschlossen]

106

Ich sehe keine Vorteile der Verwendung von Ereignissen gegenüber Delegierten, außer syntaktischem Zucker. Vielleicht verstehe ich das falsch, aber es scheint, dass die Veranstaltung nur ein Platzhalter für Delegierte ist.

Würden Sie mir die Unterschiede erklären und wann welche zu verwenden sind? Was sind die Vor- und Nachteile? Unser Code ist stark mit Ereignissen verwurzelt, und ich möchte dem auf den Grund gehen.

Wann würden Sie Delegierte für Ereignisse einsetzen und umgekehrt? Bitte geben Sie Ihre reale Erfahrung mit beiden an, etwa im Produktionscode.

halfer
quelle
Ja, es war wirklich schwer, meinen Kopf um die Unterschiede zu wickeln, sie sehen gleich aus und scheinen auf den ersten Blick dasselbe zu tun
Robert Gould
1
Siehe auch diese Frage .
Dimitri C.
1
Der Unterschied zwischen zwei Veranstaltungen und Delegierten ist eine Tatsache, keine Meinung. Die Frage fragt nach den jeweiligen Anwendungen, da sie Unterschiede in den Problemen veranschaulichen, die die Technologien lösen. Dies ist auch keine Ansichtssache, da niemand gefragt hat, welches das Beste ist. Kein Teil dieser Frage ist Ansichtssache, und diese Aussage ist auch keine Meinung. Meiner Meinung nach. Hast du dein Abzeichen bekommen?
Peter Wone

Antworten:

48

Aus technischer Sicht haben andere Antworten die Unterschiede angesprochen.

Aus semantischer Sicht sind Ereignisse Aktionen, die von einem Objekt ausgelöst werden, wenn bestimmte Bedingungen erfüllt sind. Zum Beispiel hat meine Aktienklasse eine Eigenschaft namens Limit und löst ein Ereignis aus, wenn die Aktienkurse das Limit erreichen. Diese Benachrichtigung erfolgt über ein Ereignis. Ob sich jemand wirklich um dieses Ereignis kümmert und es abonniert, ist für die Eigentümerklasse unerheblich.

Ein Delegat ist ein allgemeinerer Begriff, um ein Konstrukt zu beschreiben, das einem Zeiger in C / C ++ - Begriffen ähnelt. Alle Delegaten in .Net sind Multicast-Delegaten. Aus semantischer Sicht werden sie im Allgemeinen als eine Art Eingabe verwendet. Insbesondere sind sie eine perfekte Möglichkeit, das Strategiemuster umzusetzen . Wenn ich beispielsweise eine Liste von Objekten sortieren möchte, kann ich der Methode eine Komparatorstrategie bereitstellen, um der Implementierung mitzuteilen, wie zwei Objekte verglichen werden sollen.

Ich habe die beiden Methoden im Produktionscode verwendet. Tonnen meiner Datenobjekte benachrichtigen, wenn bestimmte Eigenschaften erfüllt sind. Das einfachste Beispiel: Wenn sich eine Eigenschaft ändert, wird ein PropertyChanged-Ereignis ausgelöst (siehe INotifyPropertyChanged-Schnittstelle). Ich habe Delegaten im Code verwendet, um verschiedene Strategien zum Verwandeln bestimmter Objekte in Zeichenfolgen bereitzustellen. Dieses spezielle Beispiel war eine verherrlichte ToString () - Liste von Implementierungen für einen bestimmten Objekttyp, um sie den Benutzern anzuzeigen.

Szymon Rozga
quelle
4
Vielleicht fehlt mir etwas, aber ist ein Event-Handler nicht eine Art Delegierter?
Powerlord
1
Meine Antwort befasst sich mit den Fragen Bearbeiten Nr. 1 und Nr. 2; Unterschiede aus Sicht der Nutzung. Für die Zwecke dieser Diskussion unterscheiden sie sich, obwohl Sie aus technischer Sicht richtig liegen. Schauen Sie sich die anderen Antworten für technische Unterschiede an.
Szymon Rozga
3
"Alle Delegierten in .Net sind Multicast-Delegierte"? Sogar Delegierte, die Werte zurückgeben?
Qwertie
5
Ja. Informationen zur Geschichte finden Sie unter msdn.microsoft.com/en-us/magazine/cc301816.aspx . Überprüfen Sie: msdn.microsoft.com/en-us/library/system.delegate.aspx . Wenn sie Werte zurückgeben, wird als Wert der letzte Delegat in der Kette ausgewertet.
Szymon Rozga
Delegaten sind Referenztypen, die auf die in der Abonnentenklasse definierten Ereignishandler verweisen. Mit anderen Worten, der Delegat wird als Verbindung zwischen dem Ereignis (im Herausgeber) und dem im Abonnenten definierten Ereignishandler verwendet. In einer Anwendung müssen mehrere Abonnenten einem Ereignis zuhören, und in solchen Szenarien bieten uns Delegierte eine effiziente Möglichkeit, Publisher und Abonnenten zu verbinden.
Josepainumkal
54

Das Schlüsselwort eventist ein Bereichsmodifikator für Multicast-Delegaten. Die praktischen Unterschiede zwischen diesem und der bloßen Erklärung eines Multicast-Delegierten sind folgende:

  • Sie können eventin einer Schnittstelle verwenden.
  • Der Aufrufzugriff auf den Multicast-Delegaten ist auf die deklarierende Klasse beschränkt. Das Verhalten ist so, als ob der Delegat für den Aufruf privat wäre. Für die Zuweisung wird der Zugriff durch einen expliziten Zugriffsmodifikator (z public event. B. ) festgelegt.

Interessanterweise können Sie Multicast-Delegaten anwenden +und anwenden. -Dies ist die Grundlage für die Syntax +=und die -=Syntax für die kombinierte Zuweisung von Delegaten zu Ereignissen. Diese drei Schnipsel sind äquivalent:

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B + C;

Beispiel zwei, das sowohl die direkte Zuordnung als auch die Kombinationszuordnung veranschaulicht.

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = B;
A += C;

Beispiel drei: bekanntere Syntax. Sie kennen wahrscheinlich die Zuweisung von null, um alle Handler zu entfernen.

B = new EventHandler(this.MethodB);
C = new EventHandler(this.MethodC);
A = null;
A += B;
A += C;

Ereignisse haben wie Eigenschaften eine vollständige Syntax, die niemand jemals verwendet. Dies:

class myExample 
{
  internal EventHandler eh;

  public event EventHandler OnSubmit 
  { 
    add 
    {
      eh = Delegate.Combine(eh, value) as EventHandler;
    }
    remove
    {
      eh = Delegate.Remove(eh, value) as EventHandler;
    }
  }

  ...
}

... macht genau das Gleiche wie folgt :

class myExample 
{
  public event EventHandler OnSubmit;
}

Die Methoden zum Hinzufügen und Entfernen sind in der von VB.NET verwendeten eher gestelzenen Syntax auffälliger (keine Operatorüberladungen).

Peter Wone
quelle
6
+ für "Aufrufzugriff auf den Multicast-Delegaten ist auf die deklarierende Klasse beschränkt" - das ist für mich der Hauptunterschied zwischen Delegierten und Ereignissen.
RichardOD
2
Ein weiterer wichtiger Unterschied (von itowlson unten erwähnt) besteht darin, dass nicht alle Ereignishandler durch Zuweisen zu einem Ereignis abgemeldet werden können, dies jedoch mit einem Delegaten. (Ihre Antwort war übrigens die nützlichste von allen).
Roman Starkov
4
So praktisch Google und Stackoverflow auch sein mögen, all dies und mehr ist in der C # -Sprachenspezifikation, die von Microsoft kostenlos öffentlich erhältlich ist, in atemberaubenden Details verfügbar. Ich weiß, dass Gott das Handbuch auf den ersten
Blick
12

Ereignisse sind syntaktischer Zucker. Sie sind lecker. Wenn ich eine Veranstaltung sehe, weiß ich, was zu tun ist. Wenn ich einen Delegierten sehe, bin ich mir nicht so sicher.

Das Kombinieren von Ereignissen mit Schnittstellen (mehr Zucker) ergibt einen köstlichen Snack. Delegierte und reine virtuelle abstrakte Klassen sind viel weniger appetitlich.

Sean
quelle
so sehe ich das auch Ich möchte eine tiefere und süßere Erklärung :)
13
Zu viel Zucker macht einen jedoch fett ... = P
Erik Forbes
5

Ereignisse sind in den Metadaten als solche gekennzeichnet. Auf diese Weise können Windows Forms- oder ASP.NET-Designer Ereignisse von bloßen Eigenschaften des Delegattyps unterscheiden und diese entsprechend unterstützen (insbesondere auf der Registerkarte Ereignisse des Eigenschaftenfensters anzeigen).

Ein weiterer Unterschied zu einer Eigenschaft vom Delegatentyp besteht darin, dass Benutzer nur Ereignishandler hinzufügen und entfernen können, während sie mit einer Eigenschaft vom Delegattyp den Wert festlegen können:

someObj.SomeCallback = MyCallback;  // okay, replaces any existing callback
someObj.SomeEvent = MyHandler;  // not okay, must use += instead

Dies hilft, Ereignisabonnenten zu isolieren: Ich kann meinen Handler einem Ereignis hinzufügen, und Sie können Ihren Handler demselben Ereignis hinzufügen, und Sie werden meinen Handler nicht versehentlich überschreiben.

itowlson
quelle
4

Obwohl Ereignisse normalerweise mit Multicast-Delegaten implementiert werden, ist es nicht erforderlich, dass sie auf diese Weise verwendet werden. Wenn eine Klasse ein Ereignis verfügbar macht, bedeutet dies, dass die Klasse zwei Methoden verfügbar macht. Ihre Bedeutungen sind im Wesentlichen:

  1. Hier ist ein Delegierter. Bitte rufen Sie es auf, wenn etwas Interessantes passiert.
  2. Hier ist ein Delegierter. Sie sollten alle Verweise darauf so schnell wie möglich zerstören (und nicht mehr aufrufen).

Die häufigste Methode für eine Klasse, ein offengelegtes Ereignis zu behandeln, besteht darin, einen Multicast-Delegaten zu definieren und alle Delegaten hinzuzufügen / zu entfernen, die an die oben genannten Methoden übergeben werden. Es ist jedoch nicht erforderlich, dass sie auf diese Weise funktionieren. Leider kann die Ereignisarchitektur einige Dinge nicht tun, die alternative Ansätze viel sauberer gemacht hätten (z. B. hätte die Abonnementmethode einen MethodInvoker zurückgegeben, der vom Abonnenten beibehalten würde; um ein Ereignis abzumelden, rufen Sie einfach die zurückgegebene Methode auf), so dass Multicast-Delegierte sind bei weitem der häufigste Ansatz.

Superkatze
quelle
4

Um die Unterschiede zu verstehen, können Sie sich diese beiden Beispiele ansehen

Beispiel mit Delegierten (Aktion in diesem Fall, bei der es sich um eine Art Delegat handelt, 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 so etwas tun

Animale 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 hatte ich die vorherigen Verhaltensweisen überschrieben, nur mit einer fehlenden +(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 wird sich auf diese Weise ändern

public class ArgsSpecial :EventArgs
   {
        public ArgsSpecial (string val)
        {
            Operation=val;
        }

        public string Operation {get; set;}
   } 



 public class Animal
    {
       public event EventHandler<ArgsSpecial> Run = delegate{} //empty delegate. In this way you are sure that value is always != null because no one outside of the class can change it

       public void RaiseEvent()
       {  
          Run(this, new ArgsSpecial("Run faster"));
       }
    }

Ereignisse aufrufen

 Animale 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 (bei Ereignissen schützt der Compiler Ihre Felder vor unerwünschtem Zugriff).
  2. Ereignisse können nicht direkt zugewiesen werden. In diesem Fall können Sie den vorherigen Fehler, den ich beim Überschreiben des Verhaltens gezeigt habe, nicht ausführen.
  3. Niemand außerhalb Ihrer Klasse kann das Ereignis auslösen.
  4. Ereignisse können in eine Schnittstellendeklaration aufgenommen werden, ein Feld jedoch nicht

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.

Sie können EventHAndlerstattdessen auch dieses Beispiel verwendenEventHandler<ArgsSpecial>

siehe hier für die Dokumentation über Eventhandler

Faby
quelle
3

Bearbeiten # 1 Wann würden Sie Delegaten für Ereignisse und vs.versa verwenden? Bitte geben Sie Ihre reale Erfahrung mit beiden an, etwa im Produktionscode.

Wenn ich meine eigenen APIs entwerfe, definiere ich Delegaten, die als Parameter an Methoden oder an die Konstruktoren von Klassen übergeben werden:

  • Damit eine Methode ein einfaches Muster für die Vorlagenmethode implementieren kann (z. B. werden die Predicateund die ActionDelegaten an die generischen .Net-Auflistungsklassen übergeben).
  • Oder damit die Klasse einen 'Rückruf' ausführen kann (normalerweise einen Rückruf zu einer Methode der Klasse, die sie erstellt hat).

Diese Delegaten sind zur Laufzeit im Allgemeinen nicht optional (dh müssen es nicht sein null).

Ich neige dazu, keine Ereignisse zu verwenden. aber wo ich Gebrauch Ereignisse tun, ich benutze sie für optional Ereignisse signalisiert Null, eine oder mehrere Clients , die könnten interessiert sein, das heißt , wenn es Sinn macht , dass eine Klasse (zB System.Windows.FormKlasse) sollte vorhanden sein und laufen , ob irgendein Kunde hat hat seinem Ereignis einen Ereignishandler hinzugefügt (z. B. ist das Ereignis "Maus nach unten" des Formulars vorhanden, es ist jedoch optional, ob ein externer Client daran interessiert ist, einen Ereignishandler für dieses Ereignis zu installieren).

ChrisW
quelle
2

Obwohl ich keine technischen Gründe dafür habe, verwende ich Ereignisse im Code im UI-Stil, dh in den höheren Codeebenen, und verwende Delegaten für Logik, die tiefer im Code liegt. Wie ich bereits sagte, könnten Sie beides verwenden, aber ich finde, dass dieses Verwendungsmuster logisch einwandfrei ist. Wenn nichts anderes, hilft es, die Arten von Rückrufen und ihre Hierarchie zu dokumentieren.


Bearbeiten: Ich denke, der Unterschied in meinen Nutzungsmustern besteht darin, dass ich es durchaus akzeptabel finde, Ereignisse zu ignorieren. Es handelt sich um Hooks / Stubs. Wenn Sie über das Ereignis Bescheid wissen müssen, hören Sie sie sich an, wenn Sie sich nicht darum kümmern Das Ereignis ignoriert es einfach. Deshalb benutze ich sie für die Benutzeroberfläche, eine Art Javascript / Browser-Ereignisstil. Wenn ich jedoch einen Delegierten habe, erwarte ich WIRKLICH, dass jemand die Aufgabe des Delegierten übernimmt und eine Ausnahme auslöst, wenn sie nicht behandelt wird.

Robert Gould
quelle
Würden Sie das näher erläutern, da ich auch Evens in der Benutzeroberfläche verwende? Ein gutes Beispiel wäre ausreichend ... danke
1

Der Unterschied zwischen Veranstaltungen und Delegierten ist viel geringer als ich dachte. Ich habe gerade ein superkurzes YouTube-Video zu diesem Thema gepostet: https://www.youtube.com/watch?v=el-kKK-7SBU

Hoffe das hilft!

Pontus Wittenmark
quelle
2
Willkommen bei Stack Overflow! Während dies theoretisch die Frage beantworten kann, wäre es vorzuziehen , die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen.
GhostCat
1

Wenn wir nur Delegate anstelle von Event verwenden, hat der Abonnent die Möglichkeit, den Delegaten selbst zu klonen (), aufzurufen (), wie unten in der Abbildung gezeigt. Welches ist nicht richtig.

Geben Sie hier die Bildbeschreibung ein

Das ist der Hauptunterschied zwischen Veranstaltung und Delegierten. Der Teilnehmer hat nur ein Recht, dh das Abhören der Ereignisse

Die ConsoleLog-Klasse abonniert Protokollereignisse über EventLogHandler

public class ConsoleLog
{
    public ConsoleLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write on console : " + str);
    }
}

Die FileLog-Klasse abonniert Protokollereignisse über EventLogHandler

public class FileLog
{
    public FileLog(Operation operation)
    {
        operation.EventLogHandler += print;
    }

    public void print(string str)
    {
        Console.WriteLine("write in File : " + str);
    }
}

Die Operationsklasse veröffentlicht Protokollereignisse

public delegate void logDelegate(string str);
public class Operation
{
    public event logDelegate EventLogHandler;
    public Operation()
    {
        new FileLog(this);
        new ConsoleLog(this);
    }

    public void DoWork()
    {
        EventLogHandler.Invoke("somthing is working");
    }
}
Narottam Goyal
quelle