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

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

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метод вызывается несколько раз, мы теряем память?

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

19.08.2008 16:03:46
Как указано в ответах ниже, отцепить его невозможно без сохранения ссылки. Тем не менее, вы можете
Benjol 17.11.2009 07:57:15
5 ОТВЕТОВ
РЕШЕНИЕ

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

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

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

16
19.08.2008 16:08:24

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

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

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

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

0
27.06.2012 13:20:33

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

4
19.08.2008 16:11:30

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

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

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

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

2
19.08.2008 16:19:24

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

3
19.08.2008 16:31:26