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