Страницы

Поиск по вопросам

понедельник, 22 октября 2018 г.

Присваивание события делегату

На просторах интернета нашел такой вот код:
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]. Особенно удивило, что если на это событие сделать много подписок, то почему-то по вызову одного делегата, они все запускаются. Растолкуйте пожалуйста.


Ответ

Дело в том, что все делегаты являются потомками MulticastDelegate. Т.е. каждый делегат -- это уже, как вы выразились, "делегатный массив", контейнер, который содержит список вызовов.
Далее, событие -- это просто член класса, имеющий тип делегата (EventHandler в данном случае) плюс некоторый синтаксис, позволяющий реализовать механизм подписки/отписки (те самые add/remove). Учитывая это две вещи, присваивание является абсолютно корректным действием (и делается для того, чтобы предотвратить NullReferenceException в многопоточной среде).
По поводу вызова всех подписок. Как я уже сказал, каждый делегат содержит в себе список всех вызовов. А потому при срабатывании события вызываются все подписки. Подписки вызываются синхронно, при это порядок их вызова не определен*.
Рекомендую почитать книгу Джеффри Рихтера "CLR via C#", глава 11, а также книгу Джона Скита "C# для профессионалов", глава 2 (есть перевод на Хабре).

*строго говоря, MulticastDelegate действительно вызывает подписки в порядке их добавления. Поэтому для field-like событий можно говорить об определенном порядке вызова. Однако этот факт является деталью реализации и полагаться на него не нужно. В случае же событий, имеющих аксессоры add/remove, полагаться на порядок нельзя вообще, поскольку в общем случае неизвестно, каким образом внутри происходит подписка.

Комментариев нет:

Отправить комментарий