ОБНОВИТЬ
Начиная с С# 6, ответ на этот вопрос:
SomeEvent?.Invoke(this, e);
Я часто слышу/читаю следующий совет:
Всегда делайте копию события, прежде чем вы проверите его для null
и уволите его. Это устранит потенциальную проблему с потоковой обработкой, когда событие станет null
в месте, где вы проверяете значение null, и где вы запускаете событие:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
Обновлено: я подумал, что, прочитав об оптимизации, это может также потребовать, чтобы член события был неустойчивым, но Джон Скит заявляет в своем ответе, что CLR не оптимизирует копию.
Между тем, для того, чтобы эта проблема даже возникла, другой поток должен был сделать что-то вроде этого:
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
Фактической последовательностью может быть эта смесь:
// Copy the event delegate before checking/calling
EventHandler copy = TheEvent;
// Better delist from event - don't want our handler called from now on:
otherObject.TheEvent -= OnTheEvent;
// Good, now we can be certain that OnTheEvent will not run...
if (copy != null)
copy(this, EventArgs.Empty); // Call any handlers on the copied list
Дело в том, что OnTheEvent
работает после того, как автор OnTheEvent
от подписки, и все же они просто OnTheEvent
от подписки, чтобы избежать этого. Конечно, на самом деле нужна специальная реализация событий с соответствующей синхронизацией в add
and remove
accessors. Кроме того, существует проблема возможных взаимоблокировок, если блокировка сохраняется во время запуска события.
Так что это Cargo Cult Programming? Похоже, что так много людей должны предпринять этот шаг, чтобы защитить свой код от нескольких потоков, когда на самом деле мне кажется, что события требуют гораздо большей осторожности, чем это, прежде чем они могут использоваться как часть многопоточного дизайна, Следовательно, люди, которые не берут на себя такую дополнительную заботу, также могут игнорировать этот совет - это просто не проблема для однопоточных программ, и на самом деле, учитывая отсутствие volatile
в большинстве онлайн-примеров кода, совет может иметь никакого эффекта вообще.
(И не проще ли просто назначить пустой delegate { }
в объявлении участника, чтобы вам никогда не нужно было проверять null
в первую очередь?)
Обновлено: В случае, если это было неясно, я понял намерение совета - избегать исключения нулевой ссылки при любых обстоятельствах. Моя точка зрения заключается в том, что это конкретное исключение ссылочной ссылки может возникнуть только в том случае, если другой поток исключает из события, и единственная причина для этого заключается в том, чтобы гарантировать, что никакие дальнейшие вызовы не будут получены через это событие, что явно НЕ достигается этой методикой, Вы будете скрывать состояние гонки - было бы лучше раскрыть это! Это исключительное исключение помогает обнаружить злоупотребление вашим компонентом. Если вы хотите, чтобы ваш компонент был защищен от злоупотреблений, вы можете следовать примеру WPF - сохранить идентификатор потока в своем конструкторе и затем выбросить исключение, если другой поток попытается напрямую взаимодействовать с вашим компонентом. Или же реализовать действительно поточно-безопасный компонент (не простая задача).
Поэтому я утверждаю, что просто выполнение этой идиомы с копией/проверкой - это культовое программирование, добавляющее беспорядок и шум к вашему коду. Для эффективной защиты от других потоков требуется гораздо больше работы.
Обновление в ответ на сообщения Eric Lippert в блоге:
Итак, главное, что я пропустил о обработчиках событий: "Обработчики событий должны быть надежными перед лицом вызова даже после того, как событие было отменено", и, очевидно, поэтому нам нужно только заботиться о возможности события делегат равен null
. Это требование для обработчиков событий документировано где угодно?
Итак: "Существуют другие способы решения этой проблемы, например, инициализация обработчика, чтобы иметь пустое действие, которое никогда не удаляется, но выполнение нулевой проверки - это стандартный шаблон".
Итак, остается один оставшийся фрагмент моего вопроса, почему явный-нуль-проверить "стандартный шаблон"? Альтернатива, назначающая пустой делегат, требует, чтобы в объявлении события добавлялось только = delegate {}
, и это устраняет эти маленькие кучки вонючей церемонии из каждого места, где происходит событие. Было бы легко убедиться, что пустой делегат дешев для создания экземпляра. Или я все еще что-то пропущу?
Несомненно, это должно быть (как предложил Джон Скит), это просто совет.NET 1.x, который не вымер, как это должно было быть сделано в 2005 году?