Может ли использование лямбд в качестве обработчиков событий вызвать утечку памяти? - PullRequest
20 голосов
/ 19 августа 2008

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

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?

Ответы [ 5 ]

16 голосов
/ 19 августа 2008

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

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

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

4 голосов
/ 19 августа 2008

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

3 голосов
/ 19 августа 2008

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

2 голосов
/ 19 августа 2008

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

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

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

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

0 голосов
/ 19 августа 2008

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

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

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

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

...