Обновление
Вот некоторые диаграммы, которые, как мы надеемся, прояснят путаницу при копировании ссылок и назначений.
Первое: копирование ссылки.
![x = y](https://i.stack.imgur.com/n5lOO.png)
На приведенной выше схеме ссылка , содержащаяся в y
, копируется в x
.Никто не говорит, что объект скопирован;имейте в виду - они указывают на один и тот же объект.
Второй: назначение новой ссылки на переменную.
![y +=](https://i.stack.imgur.com/uEAE4.png)
Забудьте об операторе +=
длямомент;Я хочу подчеркнуть, что y
присваивается другая ссылка для нового объекта.Это не влияет на x
, потому что x
является собственной переменной.Помните, только ссылка («адрес» на диаграмме) была скопирована в y
.
Третье: то же самое, только в x
.
![x +=](https://i.stack.imgur.com/RukT7.png)
На приведенных выше диаграммах изображены string
объекты только потому, что их легко представить графически.Но то же самое с делегатами (и помните, стандартные события - это просто обертки вокруг полей делегатов).Вы можете видеть, как копируя ссылку в y
в x
выше, мы создали переменную, которая не будет затронута последующими присваиваниями y
.
вся идея стандартного «исправления» состояния гонки EventHandler
, с которым мы все знакомы.
Оригинальный ответ
Вероятно, вас смущает этот хитрый маленький синтаксис:
someObject.SomeEvent += SomeEventHandler;
Важно понимать, что, как указывает Ани в своем ответе , делегаты являются неизменяемыми ссылочными типами (подумайте: точно так же, как string
).Многие разработчики ошибочно считают, что они изменчивы, потому что приведенный выше код выглядит так, будто я «добавляю» обработчик в какой-либо изменяемый список.Это не так;оператор +=
является оператором присваивания : он принимает возвращаемое значение оператора +
и присваивает его переменной слева.
(Think: int
является неизменным, и все же я могу сделать int x = 0; x += 1;
правильно? Это то же самое.)
РЕДАКТИРОВАТЬ : Да, технически это не совсем правильно.Вот что на самом деле происходит.event
на самом деле является оберткой вокруг поля делегата, которое доступно только (для внешнего кода) операторами +=
и -=
, которые скомпилированы для вызовов кadd
и remove
соответственно.Таким образом, это очень похоже на свойство, которое (обычно) является оберткой вокруг поля , где доступ к свойству и вызов =
скомпилированы для вызовов get
и set
.
Но суть остается: когда вы пишете +=
, вызываемый метод add
внутренне присваивает ссылку на новый объект внутреннему полю делегата.Я прошу прощения за упрощение этого объяснения в моем первоначальном ответе;но ключевой принцип, который нужно понять, тот же.
Кстати, я не охватываю пользовательские события, где вы можете поместить свою собственную логику в add
и remove
методы.Этот ответ относится только к «нормальному» случаю.
Другими словами, когда вы делаете это ...
EventHandler handler = SomeEvent;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
... вы действительно копируете ссылка в переменную.Теперь эта ссылка находится в локальной переменной и сама не будет изменена.Если он указывал на фактический объект во время присваивания, он будет продолжать указывать на тот же (неизменный) объект на следующей строке.Если это было , а не , указывающее на объект (null
), то оно все равно не будет указывать на объект в следующей строке.
Так что, если код в другом месте подписался или отписался на событиеиспользуя +=
, он действительно изменил исходную ссылку, чтобы указать на совершенно новый объект .Старый объект делегата все еще существует, и у вас есть ссылка на него: в вашей локальной переменной.