Потенциальный недостаток для запуска события?

Учебник по событиям в Microsoft показывает, как проверить event для null перед его запуском:

protected virtual void OnChanged(EventArgs e) 
{
    if (Changed != null)
    {   // Potential Race-condition at this point!
        Changed(this, e);
    }
}

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

protected virtual void OnChanged(EventArgs e) 
{
    ChangedEventHandler temp = Changed;  // Local copy of the EventHandler
    if (temp != null)
    {                                    // Race condition avoided by local variable.
        temp(this, e);
    }
}

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


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

// Set with empty delegate, will never be null
public event ChangedEventHandler Changed = delegate { };

protected virtual void OnChanged(EventArgs e) 
{
    // Neither Null-check nor local variable is needed; just trigger the event.
    Changed(this, e);
}

Это имеет большой смысл и довольно просто.
Однако, так как я вижу, что этот метод так редко упоминается в Интернете, я думаю, что должна быть причина, почему.
Есть ли недостаток в инициализации события с пустым делегатом, подобным этому?

Ответ 1

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

Ответ 2

  • Это небольшое повышение производительности, если событие было пустым.
  • Если вы когда-либо пишете Changed = null в классе, он сломается.

Ответ 3

В опубликованной в блоге статье Эрика Липперта он говорит:

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

Но до этого он также говорит:

Удаление кода вокруг сайта вызова [нулевая проверка] не уменьшает количество условий гонки в коде [...]. Хуже того, это затрудняет определение условий гонки сокращение окна, в котором может произойти гонка без устранения он.

Это потому, что, как он описывает, это все равно может случиться

Между нажатием значения делегата [в стек] и вызовом для его вызова [...]

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