Копирование делегатов

Я просто читал страницу события в MSDN, и я наткнулся на фрагмент кода примера, который меня озадачивает.

Этот код выглядит следующим образом:

// Make a temporary copy of the event to avoid possibility of
// a race condition if the last subscriber unsubscribes
// immediately after the null check and before the event is raised.
EventHandler<CustomEventArgs> handler = RaiseCustomEvent;

Я понимаю намерения кода, но я не вижу, как эта конкретная строка делает копию чего-либо. Все, что он делает, это копирование ссылки; он фактически не делает глубокую копию экземпляра делегата. Поэтому с этой целью он фактически не предотвращает состояние гонки.

Я пропустил что-то очевидное здесь?

Ответ 1

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

Ответ 2

Вы правы; он копирует ссылку.

Однако делегаты неизменны; при добавлении обработчика к событию создается новый делегат, объединяющий текущий обработчик с новым, и затем назначается этому полю.

Экземпляр делегата, на который ссылается поле, не может измениться, поэтому он избегает состояния гонки.

Ответ 4

Это тоже из MSDN..

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

Ответ 5

if (whatever != null) whatever(); выглядит так, что whatever никогда не является нулевым, когда вызывается whatever(), но на самом деле это не гарантирует, что в поточном сценарии. Другой поток может установить whatever = null между проверкой и вызовом.

Foo temp = whatever;
if (temp != null) temp();

Этот код устраняет возможность нулевой разыменования, поскольку temp является локальным и поэтому никогда не будет изменен другим потоком. Таким образом, это предотвращает состояние гонки. Однако это не мешает всем соответствующим условиям гонки. Эрик Липперт сделал более подробное обсуждение некоторых других проблем с кодом.