Чтобы ответить на ваши вопросы:
- Создание события блокирует поток, если все обработчики событий реализованы синхронно.
- Обработчики событий выполняются последовательно, один за другим, в том порядке, в котором они подписаны на событие.
Меня тоже интересовал внутренний механизм event
и связанные с ним операции. Поэтому я написал простую программу и ildasm
попытался ее реализовать.
Короткий ответ
- нет асинхронных операций, связанных с подпиской или вызовом событий.
- событие реализуется с помощью резервного поля делегата того же типа делегата
- подписка осуществляется с помощью
Delegate.Combine()
- отписка осуществляется с помощью
Delegate.Remove()
- Вызов осуществляется простым вызовом последнего объединенного делегата
Вот что я сделал. Программа, которую я использовал:
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();
}
}
Вот реализация Foo:
Обратите внимание, что есть поле OnCall
и событие OnCall
. Поле OnCall
, очевидно, является поддерживающим свойством. И это просто Func<int, string>
ничего особенного.
Теперь самое интересное:
add_OnCall(Func<int, string>)
remove_OnCall(Func<int, string>)
- и как
OnCall
вызывается вDo()
Как осуществляется подписка и отказ от подписки?
Вот сокращенная add_OnCall
реализация в CIL. Интересно то, что он используется Delegate.Combine
для объединения двух делегатов.
.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
Точно так же Delegate.Remove
используется в remove_OnCall
.
Как вызывается событие?
Чтобы вызвать OnCall
in Do()
, он просто вызывает последний конкатенированный делегат после загрузки аргумента:
IL_0026: callvirt instance !1 class [mscorlib]System.Func`2<int32,string>::Invoke(!0)
Как именно подписчик подписывается на событие?
И наконец Main
, что неудивительно, подписка на OnCall
событие осуществляется путем вызова add_OnCall
метода Foo
экземпляра.