Вызывается метод расширения на "нулевой" ссылке (т.е. Событие без подписчиков)?

Зло или не зло?

public static void Raise(this EventHandler handler, object sender, EventArgs args)
{
   if (handler != null)
   {
      handler(sender, args);
   }
}

// Usage:
MyButtonClicked.Raise(this, EventArgs.Empty);

// This works too! Evil?
EventHandler handler = null;
handler.Raise(this, EVentArgs.Empty);

Обратите внимание, что из-за характера методов расширения MyButtonClicked.Raise не будет вызывать исключение NullReferenceException, если MyButtonClicked имеет значение NULL. (Например, нет слушателей события MyButtonClicked).

Зло или нет?

Ответы

Ответ 1

Не злой. Я хочу, чтобы события работали таким образом по умолчанию. Может кто-нибудь объяснить, почему событие без подписчиков имеет значение null?

Ответ 2

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

public event EventHandler<EventArgs> OnClicked = delegate { };

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

Вероятно, вы можете избавиться от ключевого слова delegate в С# 3.0...

Ответ 3

Не забудьте использовать [MethodImpl(MethodImplOptions.NoInlining)], иначе возможно, что он не является потокобезопасным.

(Прочтите, что где-то давно, вспомнил об этом, googled и нашел http://blog.quantumbitdesigns.com/tag/events/)

Ответ 4

Исходя из java-фона, это всегда казалось странным для меня. Я думаю, что никто, кто слушает событие, совершенно не действует. Особенно, когда слушатели добавляются и удаляются динамически.

Мне кажется, что это один из С# gottchas, который вызывает ошибки, когда люди не знают/забывают проверять значение null каждый раз.

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

Я бы также добавил эти два метода в класс:

    public static void Raise(this EventHandler handler, object sender)
    {
        Raise(handler, sender, EventArgs.Empty);
    }

    public static void Raise<TA>(this EventHandler<TA> handler, object sender, TA args)
        where TA : EventArgs
    {
        if (handler != null)
        {
            handler(sender, args);
        }
    }

Ответ 5

Почему это было бы зло?

Его цель ясна: он вызывает событие MyButtonClicked.

Он добавляет служебные вызовы функции, но в .NET он либо будет оптимизирован, либо довольно быстро в любом случае.

Это немного тривиально, но он исправляет мою самую большую жалобу на С#.

В целом, я думаю, что это фантастическая идея, и, вероятно, украсть ее.

Ответ 6

Я бы не сказал это зло, но меня интересует, как ваш метод расширения вписывается в

protected virtual OnSomeEvent(EventArgs e){ }

и как он обрабатывает расширяемость через наследование. Предполагается ли, что все подклассы обрабатывают событие вместо переопределения метода?

Ответ 7

Хотя я бы не назвал его злым, он все еще имеет отрицательную импликацию, поскольку он добавляет лишние накладные расходы:

При вызове

myEvent.Raise(this, new EventArgs());

объект EventArgs инициализируется во всех ситуациях, даже если никто не подписался на myEvent.

При использовании

if (myEvent!= null) {
   myEvent(this, new EventArgs());
}

EventArgs инициализируется только в том случае, если кто-то подписался на myEvent.

Ответ 8

Бросок исключения, когда нет обработчиков, на самом деле не является предпочтительным. Если у него нет обработчиков, лучше быть пустым, а не нулевым.