Страницы

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

суббота, 14 декабря 2019 г.

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

#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

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

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