Подтвердить что ты не робот

Зачем кому-то подписываться на событие?

Некоторый текст перед кодом, чтобы вопрос не исказился.

class Tree
{
    public event EventHandler MadeSound;

    public void Fall() { MadeSound(this, new EventArgs()); }

    static void Main(string[] args)
    {
        Tree oaky = new Tree();
        oaky.Fall();
    }
}

Я не использовал события много в С#, но факт, что это вызовет NullRefEx, кажется странным. Ссылка EventHandler считается нулевой, поскольку в настоящее время она не имеет подписчиков, но это не означает, что событие не произошло, не так ли?

EventHandlers отличаются от стандартных делегатов ключевым словом событие. Почему дизайнеры языка не запустили их, чтобы они скрылись в пустоте, когда у них нет подписчиков? (Я понимаю, вы можете сделать это вручную, явно добавив пустой делегат).

4b9b3361

Ответ 1

Вам нужно понять, что на самом деле делает объявление вашего мероприятия. Он объявляет как событие, так и переменную. Когда вы ссылаетесь на него внутри класса, вы просто ссылаетесь на переменную, которая будет равна null, когда нет подписчиков.

Ответ 2

Ну, каноническая форма:

void OnMadeSound()
{
    if (MadeSound != null)
    {
        MadeSound(this, new EventArgs());
    }
}

public void Fall() {  OnMadeSound(); }

который немного быстрее, чем вызов пустого делегата, поэтому скорость выиграла благодаря удобству программирования.

Ответ 3

Еще один хороший способ, с которым я столкнулся, обойти это без необходимости запоминать нулевое значение:

class Tree
{
    public event EventHandler MadeSound = delegate {};

    public void Fall() { MadeSound(this, new EventArgs()); }

    static void Main(string[] args)
    {
        Tree oaky = new Tree();
        oaky.Fall();
    }
}

Обратите внимание на анонимный делегат - возможно, небольшой удар по производительности, поэтому вам нужно выяснить, какой метод (проверка нулевого или пустого делегата) лучше всего работает в вашей ситуации.

Ответ 4

Рекомендуемый шаблон (.net 2.0 +)

public class MyClass
{
    public event EventHandler<EventArgs> MyEvent; // the event

    // protected to allow subclasses to override what happens when event raised.
    protected virtual void OnMyEvent(object sender, EventArgs e)
    {
        // prevent race condition by copying reference locally
        EventHandler<EventArgs> localHandler = MyEvent;
        if (localHandler != null)
        {
            localHandler(sender, e);
        }
    }
    public void SomethingThatGeneratesEvent()
    {
        OnMyEvent(this, EventArgs.Empty);
    }
}

Я вижу много рекомендаций для пустого делегата {} в инициализаторе, но я совершенно не согласен с ним. Если вы следуете приведенному выше шаблону, вы проверяете только event != null в одном месте. Пустой делегат {} инициализатор - это отходы, потому что это дополнительный вызов на событие, он отнимает память, и он все равно может выйти из строя, если MyEvent был установлен на нуль в другом месте моего класса.

* Если ваш класс запечатан, вы бы не сделали OnMyEvent() virtual.

Ответ 5

Очень Zen, eh?

Вы должны проверить значение null, когда хотите поднять событие:

protected void OnMyEvent()
{
    if (this.MyEvent != null) this.MyEvent(this, EventArgs.Empty);
}

Было бы неплохо, если бы вам не пришлось беспокоиться об этом, но они перерывы.

Ответ 6

Джеймс предоставил хорошие технические аргументы, я также хотел бы добавить, что я видел, как люди используют это преимущество, если ни один из подписчиков не слушает событие, они будут предпринимать действия для его регистрации в коде или что-то подобное. Простой пример, но в этом контексте.

Ответ 7

Какой смысл поднимать событие, если никто не слушает? Технически, именно так, как С# решил реализовать его.

В С# событие является делегатом с некоторыми специальными перьями. Делегат в этом случае может рассматриваться как связанный список указателей на функции (для методов обработчиков подписчиков). Когда вы запускаете событие, каждый указатель функции вызывается по очереди. Первоначально делегат является нулевым объектом, как и все остальное. Когда вы выполняете + = для первого действия подписки, вызывается Delegate.Combine, который создает экземпляр списка. (Вызов null.Invoke() выдает пустое исключение - при запуске события.)

Если вы все еще чувствуете, что "это не должно быть", используйте вспомогательный класс EventsHelper, как упоминалось здесь, со старым и улучшенным "публикацией защитных событий" http://weblogs.asp.net/rosherove/articles/DefensiveEventPublishing.aspx

Ответ 8

Использование метода расширения было бы полезно в этом сценарии.

public static class EventExtension
{
    public static void RaiseEvent<T>(this EventHandler<T> handler, object obj, T args) where T : EventArgs
    {
        if (handler != null)
        {
            handler(obj, args);
        }
    }
}

Затем его можно использовать, как показано ниже.

public event EventHandler<YourEventArgs> YourEvent;
...
YourEvent.RaiseEvent(this, new YourEventArgs());

Ответ 9

Спасибо за ответы. Я понимаю, почему происходит исключение NullReferenceException и как обойти его.

Гишу сказал

Какой смысл поднимать событие, если никто не слушает?

Ну, может быть, это терминология. Привлекательность системы "событий" кажется мне, что вся ответственность за последствия произошедшего события должна быть на наблюдателей, а не на исполнителя.


Возможно, лучше спросить: если поле делегата объявлено с ключевым словом event перед ним, почему компилятор не переводит все экземпляры:

MadeSound(this, EventArgs.Empty)

к

if (MadeSound != null) { MadeSound(this, EventArgs.Empty); }

за кулисами так же, как и другие ярлыки синтаксиса? Количество стандартных методов проверки OnSomeEvent, которые должны писать вручную, должно быть колоссальным.