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