So beantworten Sie Ihre Fragen:
- Das Auslösen eines Ereignisses blockiert den Thread, wenn die Ereignishandler alle synchron implementiert sind.
- Die Ereignishandler werden nacheinander in der Reihenfolge ausgeführt, in der sie das Ereignis abonniert haben.
Auch ich war neugierig auf den internen Mechanismus event
und die damit verbundenen Operationen. Also schrieb ich ein einfaches Programm und ildasm
stöberte in seiner Implementierung herum.
Die kurze Antwort lautet
- Es gibt keine asynchrone Operation zum Abonnieren oder Aufrufen der Ereignisse.
- Das Ereignis wird mit einem Hintergrunddelegatenfeld desselben Delegatentyps implementiert
- Das Abonnieren ist abgeschlossen mit
Delegate.Combine()
- Das Abbestellen erfolgt mit
Delegate.Remove()
- Das Aufrufen erfolgt durch einfaches Aufrufen des endgültigen kombinierten Delegaten
Folgendes habe ich getan. Das Programm, das ich verwendet habe:
public class Foo
{
// cool, it can return a value! which value it returns if there're multiple
// subscribers? answer (by trying): the last subscriber.
public event Func<int, string> OnCall;
private int val = 1;
public void Do()
{
if (OnCall != null)
{
var res = OnCall(val++);
Console.WriteLine($"publisher got back a {res}");
}
}
}
public class Program
{
static void Main(string[] args)
{
var foo = new Foo();
foo.OnCall += i =>
{
Console.WriteLine($"sub2: I've got a {i}");
return "sub2";
};
foo.OnCall += i =>
{
Console.WriteLine($"sub1: I've got a {i}");
return "sub1";
};
foo.Do();
foo.Do();
}
}
Hier ist die Implementierung von Foo:
Beachten Sie, dass es ein Feld OnCall
und ein Ereignis gibt OnCall
. Das Feld OnCall
ist offensichtlich die Backing-Eigenschaft. Und es ist nur ein Func<int, string>
, nichts Besonderes hier.
Nun sind die interessanten Teile:
add_OnCall(Func<int, string>)
remove_OnCall(Func<int, string>)
- und wie
OnCall
wird in aufgerufenDo()
Wie wird das Abonnieren und Abbestellen implementiert?
Hier ist die abgekürzte add_OnCall
Implementierung in CIL. Der interessante Teil ist, dass Delegate.Combine
zwei Delegaten verkettet werden.
.method public hidebysig specialname instance void
add_OnCall(class [mscorlib]System.Func`2<int32,string> 'value') cil managed
{
// ...
.locals init (class [mscorlib]System.Func`2<int32,string> V_0,
class [mscorlib]System.Func`2<int32,string> V_1,
class [mscorlib]System.Func`2<int32,string> V_2)
IL_0000: ldarg.0
IL_0001: ldfld class [mscorlib]System.Func`2<int32,string> ConsoleApp1.Foo::OnCall
// ...
IL_000b: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
// ...
} // end of method Foo::add_OnCall
Ebenso Delegate.Remove
wird in verwendet remove_OnCall
.
Wie wird ein Ereignis aufgerufen?
Zum Aufrufen OnCall
in Do()
, ruft er einfach die letzte verkettete Delegierten nach dem ARG - Laden:
IL_0026: callvirt instance !1 class [mscorlib]System.Func`2<int32,string>::Invoke(!0)
Wie genau abonniert ein Abonnent eine Veranstaltung?
Und schließlich Main
erfolgt das Abonnieren des OnCall
Ereignisses , nicht überraschend, durch Aufrufen der add_OnCall
Methode für die Foo
Instanz.