Может ли использование lambdas в качестве обработчиков событий вызвать утечку памяти?

Скажем, что мы имеем следующий метод:

private MyObject foo = new MyObject();

// and later in the class

public void PotentialMemoryLeaker(){
  int firedCount = 0;
  foo.AnEvent += (o,e) => { firedCount++;Console.Write(firedCount);};
  foo.MethodThatFiresAnEvent();
}

Если класс с этим методом инстанцируется и метод PotentialMemoryLeaker вызывается несколько раз, мы утечка памяти?

Есть ли способ отменить этот обработчик события lambda после того, как мы закончим вызов MethodThatFiresAnEvent?

Ответ 1

Да, сохраните его в переменной и отцепите.

DelegateType evt = (o, e) => { firedCount++; Console.Write(firedCount); };
foo.AnEvent += evt;
foo.MethodThatFiresAnEvent();
foo.AnEvent -= evt;

И да, если вы этого не сделаете, вы будете утечка памяти, так как каждый раз подключаете новый объект делегата. Вы также заметите это, потому что каждый раз, когда вы вызываете этот метод, он будет выдавать на консоль все большее количество строк (а не только увеличивающееся число, но для одного вызова метода MethodThatFiresAnEvent он будет выгружать любое количество элементов, один раз для каждый подключенный анонимный метод).

Ответ 2

У вас не будет просто утечки памяти, вы также будете получать свою лямбду несколько раз. Каждый вызов "PotentialMemoryLeaker" добавит еще одну копию лямбда в список событий, и каждая копия будет вызываться при запуске "AnEvent".

Ответ 3

Хорошо, вы можете расширить то, что было сделано здесь, чтобы сделать делегатов более безопасными (без утечек памяти)

Ответ 4

Ваш пример просто компилируется в закрытый внутренний класс с именем компилятора (с полем firedCount и методом с использованием компилятора). Каждый вызов PotentialMemoryLeaker создает новый экземпляр класса замыкания, в котором foo сохраняет ссылку посредством делегирования для одного метода.

Если вы не ссылаетесь на весь объект, который владеет PotentialMemoryLeaker, то все это будет собирать мусор. В противном случае вы можете установить foo в нулевой или пустой список обработчиков событий foo, написав это:

foreach (var handler in AnEvent.GetInvocationList()) AnEvent -= handler;

Конечно, вам нужен доступ к закрытым членам класса MyObject.

Ответ 5

Да так же, как обычные обработчики событий могут вызывать утечки. Поскольку лямбда фактически изменена на:

someobject.SomeEvent += () => ...;
someobject.SomeEvent += delegate () {
    ...
};

// unhook
Action del = () => ...;
someobject.SomeEvent += del;
someobject.SomeEvent -= del;

Итак, в основном это всего лишь короткая рука за то, что мы использовали в 2.0 все эти годы.