Per rispondere alle tue domande:
- La generazione di un evento blocca il thread se i gestori di eventi sono tutti implementati in modo sincrono.
- I gestori di eventi vengono eseguiti in sequenza, uno dopo l'altro, nell'ordine in cui sono iscritti all'evento.
Anch'io ero curioso del meccanismo interno di event
e delle sue operazioni correlate. Quindi ho scritto un semplice programma e ne ho ildasm
curati l'implementazione.
La risposta breve è
- non ci sono operazioni asincrone coinvolte nella sottoscrizione o nel richiamo degli eventi.
- L'evento viene implementato con un campo delegato di supporto dello stesso tipo di delegato
- l'iscrizione è terminata con
Delegate.Combine()
- l'annullamento dell'iscrizione è fatto con
Delegate.Remove()
- La chiamata viene eseguita semplicemente invocando il delegato combinato finale
Ecco cosa ho fatto. Il programma che ho usato:
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();
}
}
Ecco l'implementazione di Foo:
Nota che sono presenti un campo OnCall
e un evento OnCall
. Il campo OnCall
è ovviamente la proprietà di supporto. Ed è semplicemente un fileFunc<int, string>
, niente di speciale qui.
Ora le parti interessanti sono:
add_OnCall(Func<int, string>)
remove_OnCall(Func<int, string>)
- e come
OnCall
viene invocato inDo()
Come vengono implementate la sottoscrizione e l'annullamento della sottoscrizione?
Ecco l' add_OnCall
implementazione abbreviata in CIL. La parte interessante è che utilizza Delegate.Combine
per concatenare due delegati.
.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
Allo stesso modo, Delegate.Remove
viene utilizzato in remove_OnCall
.
Come viene invocato un evento?
Per richiamare OnCall
in Do()
, si chiama semplicemente il delegato concatenato finale dopo aver caricato l'arg:
IL_0026: callvirt instance !1 class [mscorlib]System.Func`2<int32,string>::Invoke(!0)
In che modo esattamente un abbonato si iscrive a un evento?
Infine, Main
non sorprendentemente, la sottoscrizione OnCall
all'evento viene eseguita chiamando il add_OnCall
metodo Foo
sull'istanza.