Частное поле, снятое анонимным делегатом
class A
{
public event EventHandler AEvent;
}
class B
{
private A _foo;
private int _bar;
public void AttachToAEvent()
{
_foo.AEvent += delegate()
{
...
UseBar(_bar);
...
}
}
}
Так как делегат захватывает переменную this._bar, он неявно удерживается в экземпляре B? Можно ли ссылаться на экземпляр B через обработчик событий и захваченную переменную экземпляром A?
Было бы иначе, если бы _bar была локальной переменной метода AttachToAEvent?
Так как в моем случае экземпляр A живет намного дольше и намного меньше, чем экземпляр B, я беспокоюсь, чтобы это вызвало утечку памяти.
Ответы
Ответ 1
Это проще всего понять, посмотрев код, сгенерированный компилятором, который похож на:
public void AttachToAEvent()
{
_foo.AEvent += new EventHandler(this.Handler);
}
[CompilerGenerated]
private void Handler(object sender, EventArgs e)
{
this.UseBar(this._bar);
}
Как ясно видно, созданный делегат является делегатом-экземпляром (задает метод экземпляра для объекта) и поэтому должен содержать ссылку на этот экземпляр объекта.
Так как делегат захватывает переменную this._bar, она неявно удерживает экземпляр B?
На самом деле анонимный метод захватывает только this
(не this._bar
). Как видно из сгенерированного кода, построенный делегат действительно будет ссылаться на экземпляр B
. Он должен; как еще можно было бы читать поле по требованию всякий раз, когда делегат выполняется? Помните, что переменные захватываются, а не значения.
Так как в моем случае экземпляр A живет намного дольше и намного меньше чем экземпляр B, я волнуюсь, чтобы вызвать "утечку памяти", сделав это.
Да, у вас есть все основания быть. Пока экземпляр A
доступен, подписчик событий B
по-прежнему будет доступен. Если вы не хотите походить на слабые события, вам нужно переписать это, чтобы обработчик не был зарегистрирован, когда он больше не требуется.
Было бы иначе, если бы _bar была локальной переменной Метод AttachToAEvent?
Да, это было бы, так как захваченная переменная тогда стала бы bar
локальным, а не this
.
Но если предположить, что UseBar
- это метод-экземпляр, ваша "проблема" (если вы хотите, чтобы об этом думали так) только что ухудшилась. Теперь компилятор должен создать event-listener, который "запоминает" как локальный, так и содержащий экземпляр объекта B
.
Это достигается путем создания объекта замыкания и превращения его (действительно его метода экземпляра) в цель делегата.
public void AttachToAEvent(int _bar)
{
Closure closure = new Closure();
closure._bar = _bar;
closure._bInstance = this;
_foo.AEvent += new EventHandler(closure.Handler);
}
[CompilerGenerated]
private sealed class Closure
{
public int _bar;
public B _bInstance;
public void Handler(object sender , EventArgs e)
{
_bInstance.UseBar(this._bar);
}
}
Ответ 2
Ответ Ani правильный. Подведение итогов и добавление некоторых деталей:
Поскольку делегат захватывает переменную this._bar, она неявно удерживается в экземпляре B?
Да. "this".
Будет ли ссылаться на экземпляр B через обработчик событий и захваченную переменную экземпляром A?
Да.
Было бы иначе, если бы _bar были локальной переменной метода AttachToAEvent?
Да. В этом случае объект закрытия будет удерживаться на локальном уровне; локальное будет реализовано как поле замыкания.
Так как в моем случае экземпляр A живет намного дольше и намного меньше, чем экземпляр B, я беспокоюсь, чтобы это вызвало утечку памяти.
Вы абсолютно правы, чтобы волноваться. Ваша ситуация уже плохая, но на самом деле ситуация может быть значительно хуже, если в игре есть две анонимные функции. В настоящее время все анонимные функции в одном и том же локальном пространстве декларации переменных разделяют общее закрытие, а это означает, что время жизни всех внешних внешних переменных (включая "this") увеличивается до самого длинного из всех из них. Более подробную информацию см. В моей статье на эту тему:
http://blogs.msdn.com/b/ericlippert/archive/2007/06/06/fyi-c-and-vb-closures-are-per-scope.aspx
Мы надеемся исправить это в гипотетической будущей версии С#; мы могли бы лучше разделить блокировки вместо того, чтобы создать одно большое закрытие. Однако это не произойдет в ближайшее время.
Кроме того, функция "async/await" на С# 5 также, вероятно, усугубит ситуации, когда локальные жители будут жить дольше, чем вы ожидали. Никто из нас не в восторге от этого, но, как говорится, совершенным является враг удивительного. У нас есть некоторые идеи о том, как мы можем настроить codegen асинхронных блоков, чтобы улучшить ситуацию, но не promises.
Ответ 3
Если вы добавляете анонимный метод к событию и хотите его уважать, вам нужно будет установить событие в null или сохранить делегата в списке, чтобы впоследствии вы могли "- =" его от вашего события.
Но да, вы можете получить "утечку памяти" из объектов, на которые ссылается делегат, прикрепленный к событию.