Закрытое поле захвачено анонимным делегатом - PullRequest
12 голосов
/ 07 декабря 2011
class A
{
   public event EventHandler AEvent;
}
class B
{
   private A _foo;
   private int _bar;

   public void AttachToAEvent()
   {
      _foo.AEvent += delegate()
      {
         ...
         UseBar(_bar);
         ...
      }
   }
} 

Поскольку delegate захватывает переменную this._bar, неявно ли она относится к экземпляру B?Будет ли ссылка на экземпляр B через обработчик событий и перехваченная переменная экземпляром A?

Будет ли иначе, если _bar будет локальной переменной метода AttachToAEvent?

Поскольку в моем случае экземпляр A живет намного дольше и намного меньше экземпляра B, я обеспокоен тем, что при этом произойдет «утечка памяти».

Ответы [ 3 ]

13 голосов
/ 07 декабря 2011

Ани ответ правильный.Резюмируя и добавляя некоторые детали:

Поскольку делегат захватывает переменную 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

Мы надеемся исправить это в гипотетической будущей версии C #;мы могли бы лучше разделить замыкания вместо создания одного большого замыкания.Однако это не произойдет в ближайшее время.

Более того, функция «асинхронное ожидание» в C # 5 также, вероятно, усугубит ситуации, в которых местные жители будут жить дольше, чем вы ожидаете.Никто из нас не в восторге от этого, но, как говорится, совершенство - враг удивительного.У нас есть некоторые идеи о том, как мы можем изменить кодовый блок асинхронных блоков, чтобы улучшить ситуацию, но никаких обещаний.

10 голосов
/ 07 декабря 2011

Это проще всего понять, посмотрев код, сгенерированный компилятором, который похож на:

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 является методом экземпляра, ваша «проблема» (если вы хотите так думать об этом) только ухудшилась.Теперь компилятору необходимо сгенерировать прослушиватель событий, который «запоминает» как локальный, так и содержащий 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);
    }
}
0 голосов
/ 07 декабря 2011

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

Но да, вы можете получить «утечку памяти» из объектов, на которые ссылается делегат, прикрепленный к событию.

...