#c_sharp
На просторах интернета нашел такой вот код:
using System;
class a
{
public event EventHandler Ev;
public void EventStart()
{
EventHandler temp = Ev;
if (temp != null)
temp(this, EventArgs.Empty);
}
}
class demo
{
static void Main()
{
a A = new a();
A.Ev += (o, e) => { Console.WriteLine(1); };
A.Ev += (o, e) => { Console.WriteLine(2); };
A.Ev += (o, e) => { Console.WriteLine(3); };
A.EventStart();
}
}
И меня очень удивила эта строчка EventHandler temp = Ev; почему присваивание происходит
нормально ? Ведь событие и делегат это разные вещи. Событие это поле с аксессорами
add remove, которые добавляют/удаляют методы (делегаты) в "делегатный" массив. То есть
если грубо то это присваивание что-то вроде Action a = new Action[n]. Особенно удивило,
что если на это событие сделать много подписок, то почему-то по вызову одного делегата,
они все запускаются. Растолкуйте пожалуйста.
Ответы
Ответ 1
Дело в том, что все делегаты являются потомками MulticastDelegate. Т.е. каждый делегат -- это уже, как вы выразились, "делегатный массив", контейнер, который содержит список вызовов. Далее, событие -- это просто член класса, имеющий тип делегата (EventHandler в данном случае) плюс некоторый синтаксис, позволяющий реализовать механизм подписки/отписки (те самые add/remove). Учитывая это две вещи, присваивание является абсолютно корректным действием (и делается для того, чтобы предотвратить NullReferenceException в многопоточной среде). По поводу вызова всех подписок. Как я уже сказал, каждый делегат содержит в себе список всех вызовов. А потому при срабатывании события вызываются все подписки. Подписки вызываются синхронно, при это порядок их вызова не определен*. Рекомендую почитать книгу Джеффри Рихтера "CLR via C#", глава 11, а также книгу Джона Скита "C# для профессионалов", глава 2 (есть перевод на Хабре). *строго говоря, MulticastDelegate действительно вызывает подписки в порядке их добавления. Поэтому для field-like событий можно говорить об определенном порядке вызова. Однако этот факт является деталью реализации и полагаться на него не нужно. В случае же событий, имеющих аксессоры add/remove, полагаться на порядок нельзя вообще, поскольку в общем случае неизвестно, каким образом внутри происходит подписка.Ответ 2
В дополнение к правильному ответу @andreycha: Дело в том, что для событий, определённых без явного указания add/remove, внутренний делегат, в котором хранится список подписчиков, доступен по тому же имени, что и событие, изнутри класса. Снаружи доступно, разумеется, только событие, иначе это было бы нарушением инкапсуляции. Дополнительное чтение по теме: https://ru.stackoverflow.com/a/226698/10105
Комментариев нет:
Отправить комментарий