Почему кто-то должен быть подписан на событие? - PullRequest
12 голосов
/ 16 октября 2008

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

class Tree
{
    public event EventHandler MadeSound;

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

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

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

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

Ответы [ 9 ]

6 голосов
/ 16 октября 2008

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

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

public void Fall() {  OnMadeSound(); }

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

4 голосов
/ 17 октября 2008

Рекомендуемый шаблон (.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() виртуальным.

4 голосов
/ 16 октября 2008

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

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();
    }
}

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

3 голосов
/ 16 октября 2008

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

3 голосов
/ 16 октября 2008

Очень дзен, а?

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

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

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

2 голосов
/ 16 октября 2008

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

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

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

2 голосов
/ 16 октября 2008

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

1 голос
/ 06 августа 2009

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

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());
0 голосов
/ 16 октября 2008

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

Гишу сказал

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

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


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

MadeSound(this, EventArgs.Empty)

до

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...