Копирование делегатов
Я просто читал страницу события в 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
Вы правы; он копирует ссылку.
Однако делегаты неизменны; при добавлении обработчика к событию создается новый делегат, объединяющий текущий обработчик с новым, и затем назначается этому полю.
Экземпляр делегата, на который ссылается поле, не может измениться, поэтому он избегает состояния гонки.
Ответ 3
Эрик Липперт уже рассмотрел это в очень подробном post.
Ответ 4
Это тоже из MSDN..
"Список вызовов делегата - это упорядоченный набор делегатов, в котором каждый элемент списка вызывает точно один из методов, представленных делегатом. Список вызовов может содержать повторяющиеся методы. вызовы вызывают в том порядке, в котором они отображаются в списке вызовов. Делегат пытается вызвать каждый метод в своем списке вызовов; дубликаты вызывается один раз для каждого момента, когда они появляются в списке вызовов. Делегаты неизменяемы, после создания список вызовов делегата не изменяется."
Ответ 5
if (whatever != null) whatever();
выглядит так, что whatever
никогда не является нулевым, когда вызывается whatever()
, но на самом деле это не гарантирует, что в поточном сценарии. Другой поток может установить whatever = null
между проверкой и вызовом.
Foo temp = whatever;
if (temp != null) temp();
Этот код устраняет возможность нулевой разыменования, поскольку temp
является локальным и поэтому никогда не будет изменен другим потоком. Таким образом, это предотвращает состояние гонки. Однако это не мешает всем соответствующим условиям гонки. Эрик Липперт сделал более подробное обсуждение некоторых других проблем с кодом.