Mir ist klar, dass diese Frage über 10 Jahre alt ist, aber es scheint mir, dass nicht nur die offensichtlichste Antwort nicht angesprochen wurde, sondern dass aus der Frage möglicherweise nicht wirklich ein gutes Verständnis dafür hervorgeht, was unter der Decke vor sich geht. Darüber hinaus gibt es weitere Fragen zur verspäteten Bindung und was dies für Delegierte und Lambdas bedeutet (dazu später mehr).
Sprechen Sie zuerst den 800 Pfund schweren Elefanten / Gorilla im Raum an, wenn Sie eventvs Action<T>/ wählen möchten Func<T>:
- Verwenden Sie ein Lambda, um eine Anweisung oder Methode auszuführen. Verwenden
eventSie diese Option, wenn Sie mehr von einem Pub / Sub-Modell mit mehreren Anweisungen / Lambdas / Funktionen möchten, die ausgeführt werden (dies ist auf
Anhieb ein großer Unterschied).
- Verwenden Sie ein Lambda, wenn Sie Anweisungen / Funktionen zu Ausdrucksbäumen kompilieren möchten. Verwenden Sie Delegaten / Ereignisse, wenn Sie an einer traditionelleren Spätbindung teilnehmen möchten, wie sie beispielsweise in Reflection und COM Interop verwendet wird.
Lassen Sie uns als Beispiel für ein Ereignis eine einfache und standardmäßige Reihe von Ereignissen mithilfe einer kleinen Konsolenanwendung wie folgt verkabeln:
public delegate void FireEvent(int num);
public delegate void FireNiceEvent(object sender, SomeStandardArgs args);
public class SomeStandardArgs : EventArgs
{
public SomeStandardArgs(string id)
{
ID = id;
}
public string ID { get; set; }
}
class Program
{
public static event FireEvent OnFireEvent;
public static event FireNiceEvent OnFireNiceEvent;
static void Main(string[] args)
{
OnFireEvent += SomeSimpleEvent1;
OnFireEvent += SomeSimpleEvent2;
OnFireNiceEvent += SomeStandardEvent1;
OnFireNiceEvent += SomeStandardEvent2;
Console.WriteLine("Firing events.....");
OnFireEvent?.Invoke(3);
OnFireNiceEvent?.Invoke(null, new SomeStandardArgs("Fred"));
//Console.WriteLine($"{HeightSensorTypes.Keyence_IL030}:{(int)HeightSensorTypes.Keyence_IL030}");
Console.ReadLine();
}
private static void SomeSimpleEvent1(int num)
{
Console.WriteLine($"{nameof(SomeSimpleEvent1)}:{num}");
}
private static void SomeSimpleEvent2(int num)
{
Console.WriteLine($"{nameof(SomeSimpleEvent2)}:{num}");
}
private static void SomeStandardEvent1(object sender, SomeStandardArgs args)
{
Console.WriteLine($"{nameof(SomeStandardEvent1)}:{args.ID}");
}
private static void SomeStandardEvent2(object sender, SomeStandardArgs args)
{
Console.WriteLine($"{nameof(SomeStandardEvent2)}:{args.ID}");
}
}
Die Ausgabe sieht wie folgt aus:

Wenn Sie dasselbe mit Action<int>oder tun Action<object, SomeStandardArgs>würden, würden Sie nur SomeSimpleEvent2und sehen SomeStandardEvent2.
Also, was ist in dir los event?
Wenn wir erweitern FireNiceEvent, generiert der Compiler tatsächlich Folgendes (ich habe einige Details in Bezug auf die Thread-Synchronisation weggelassen, die für diese Diskussion nicht relevant sind):
private EventHandler<SomeStandardArgs> _OnFireNiceEvent;
public void add_OnFireNiceEvent(EventHandler<SomeStandardArgs> handler)
{
Delegate.Combine(_OnFireNiceEvent, handler);
}
public void remove_OnFireNiceEvent(EventHandler<SomeStandardArgs> handler)
{
Delegate.Remove(_OnFireNiceEvent, handler);
}
public event EventHandler<SomeStandardArgs> OnFireNiceEvent
{
add
{
add_OnFireNiceEvent(value)
}
remove
{
remove_OnFireNiceEvent(value)
}
}
Der Compiler generiert eine private Delegatenvariable, die für den Klassennamensraum, in dem sie generiert wird, nicht sichtbar ist. Dieser Delegat wird für das Abonnementmanagement und die verspätete Teilnahme verwendet, und die öffentlich zugängliche Oberfläche ist die vertraute +=und die -=Betreiber, die wir alle kennen und lieben gelernt haben :)
Sie können den Code für die Handler zum Hinzufügen / Entfernen anpassen, indem Sie den Bereich des FireNiceEventDelegaten in "Geschützt" ändern . Auf diese Weise können Entwickler den Hooks jetzt benutzerdefinierte Hooks hinzufügen, z. B. Protokollierungs- oder Sicherheitshooks. Dies führt zu einigen sehr leistungsstarken Funktionen, die jetzt einen benutzerdefinierten Zugriff auf Abonnements basierend auf Benutzerrollen usw. ermöglichen. Können Sie dies mit Lambdas tun? (Eigentlich können Sie Ausdrucksbäume benutzerdefiniert kompilieren, aber das geht über den Rahmen dieser Antwort hinaus).
Um einige Punkte aus einigen der Antworten hier anzusprechen:
Es gibt wirklich keinen Unterschied in der 'Sprödigkeit' zwischen dem Ändern der Args-Liste in Action<T>und dem Ändern der Eigenschaften in einer von abgeleiteten Klasse EventArgs. Beide erfordern nicht nur eine Kompilierungsänderung, sondern auch eine öffentliche Schnittstelle und eine Versionierung. Kein Unterschied.
In Bezug darauf, welcher Industriestandard ist, hängt dies davon ab, wo und warum dieser verwendet wird. Action<T>und solche werden häufig in IoC und DI verwendet und eventwerden häufig in Nachrichtenrouting wie GUI- und MQ-Frameworks verwendet. Beachten Sie, dass ich oft gesagt habe , nicht immer .
Delegierte haben andere Lebensdauern als Lambdas. Man muss sich auch der Gefangennahme bewusst sein ... nicht nur mit dem Schließen, sondern auch mit dem Gedanken "Schau, was die Katze hineingezogen hat". Dies wirkt sich sowohl auf den Speicherbedarf / die Lebensdauer als auch auf die Verwaltung aus.
Eine weitere Sache, auf die ich bereits hingewiesen habe ... der Begriff der späten Bindung. Sie werden dies häufig sehen, wenn Sie ein Framework wie LINQ verwenden, wenn ein Lambda "live" wird. Dies unterscheidet sich stark von der späten Bindung eines Delegierten, die mehr als einmal auftreten kann (dh das Lambda ist immer vorhanden, die Bindung erfolgt jedoch bei Bedarf so oft wie erforderlich), im Gegensatz zu einem Lambda, das, sobald es auftritt, durchgeführt wird - Die Magie ist verschwunden und die Methode (n) / Eigenschaft (en) werden immer gebunden. Etwas zu beachten.