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


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

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).

зло или нет?

8 59

8 ответов:

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

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

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

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

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

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

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

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

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

скрытие этой детали реализации кажется хорошим планом, так как это не помогает читаемости проверять значения 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);
        }
    }

Почему это должно быть злом?

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

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

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

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

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

protected virtual OnSomeEvent(EventArgs e){ }

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

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

при вызове

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

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

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

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

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

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