UPDATE
Начиная с C # 6, ответ на этот вопрос:
SomeEvent?.Invoke(this, e);
Я часто слышу / читаю следующий совет:
Всегда делайте копию события, прежде чем проверять его на 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
запускается после того, как автор отписался, и все же они просто отписались специально, чтобы избежать этого. Конечно, что действительно необходимо, так это пользовательская реализация событий с соответствующей синхронизацией в методах доступа add
и remove
. Кроме того, существует проблема возможных взаимоблокировок, если блокировка удерживается во время запуска события.
Так это Программирование грузового культа ? Кажется, так - многие люди должны предпринять этот шаг, чтобы защитить свой код от нескольких потоков, тогда как на самом деле мне кажется, что события требуют гораздо большего внимания, чем это, прежде чем их можно будет использовать как часть многопоточного дизайна. , Следовательно, люди, которые не проявляют такой дополнительной заботы, могут также проигнорировать этот совет - это просто не проблема для однопоточных программ, и фактически, учитывая отсутствие volatile
в большинстве примеров кода в Интернете, совет может не иметь никакого эффекта вообще.
(И не намного ли проще просто присвоить пустую delegate { }
в декларации члена, чтобы вам никогда не приходилось проверять null
в первую очередь?)
Обновлено: В случае, если неясно, я понял намерение совета - избегать исключения нулевой ссылки при любых обстоятельствах. Моя точка зрения заключается в том, что это исключение с нулевой ссылкой может возникнуть только в том случае, если другой поток исключен из события, и единственная причина для этого состоит в том, чтобы гарантировать, что никакие дополнительные вызовы не будут получены через это событие, что явно НЕ достигается этим методом , Вы бы скрывали состояние гонки - было бы лучше раскрыть это! Это нулевое исключение помогает обнаружить злоупотребление вашим компонентом. Если вы хотите, чтобы ваш компонент был защищен от злоупотреблений, вы можете последовать примеру WPF - сохранить идентификатор потока в конструкторе и затем выдать исключение, если другой поток пытается напрямую взаимодействовать с вашим компонентом. Или же реализовать действительно потокобезопасный компонент (задача не из легких).
Таким образом, я утверждаю, что просто использование этой идиомы копирования / проверки является программированием культового груза, добавляющим беспорядок и шум в ваш код. Чтобы реально защитить себя от других потоков, требуется гораздо больше работы.
Обновление в ответ на сообщения Эрика Липперта в блоге:
Итак, есть одна важная вещь, которую я упустил в обработчиках событий: «обработчики событий должны быть устойчивыми перед вызовом даже после того, как событие было отписано», и, следовательно, поэтому нам нужно заботиться только о возможности делегата мероприятия null
. Задокументировано ли это требование к обработчикам событий?
И так: «Существуют и другие способы решения этой проблемы; например, инициализация обработчика для получения пустого действия, которое никогда не удаляется. Но выполнение нулевой проверки является стандартным шаблоном».
Итак, оставшийся фрагмент моего вопроса: почему явно-нулевая проверка "стандартного шаблона"? Альтернатива, назначающая пустой делегат, требует добавления только = delegate {}
к объявление события, и это устраняет эти маленькие груды вонючей церемонии из каждого места, где происходит событие. Было бы легко убедиться, что пустой делегат дешевый для создания экземпляра. Или я все еще что-то упускаю?
Конечно, должно быть, (как предположил Джон Скит) это всего лишь совет .NET 1.x, который не исчез, как это должно было быть в 2005 году?