Скажем, у нас есть следующий метод:
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?





Да, сохраните его в переменной и отцепите.
DelegateType evt = (o, e) => { firedCount++; Console.Write(firedCount); };
foo.AnEvent += evt;
foo.MethodThatFiresAnEvent();
foo.AnEvent -= evt;
И да, если вы этого не сделаете, у вас будет утечка памяти, поскольку вы будете каждый раз подключать новый объект делегата. Вы также заметите это, потому что каждый раз, когда вы вызываете этот метод, он выводит на консоль все большее количество строк (не только увеличивающееся число, но и для одного вызова MethodThatFiresAnEvent, он сбрасывает любое количество элементов один раз за каждый подключил анонимный метод).
Да, точно так же, как обычные обработчики событий могут вызывать утечки. Поскольку лямбда фактически изменена на:
someobject.SomeEvent += () => ...;
someobject.SomeEvent += delegate () {
...
};
// unhook
Action del = () => ...;
someobject.SomeEvent += del;
someobject.SomeEvent -= del;
Так что по сути это всего лишь сокращение от того, что мы использовали в 2.0 все эти годы.
Вы не просто потеряете память, вы также получите несколько вызовов лямбды. Каждый вызов PotentialMemoryLeaker будет добавлять еще одну копию лямбда-выражения в список событий, и каждая копия будет вызываться при срабатывании AnEvent.
Ваш пример просто компилируется в частный внутренний класс с именем компилятора (с полем firedCount и методом с именем компилятора). Каждый вызов PotentialMemoryLeaker создает новый экземпляр класса закрытия, на который foo сохраняет ссылку посредством делегата на единственный метод.
Если вы не ссылаетесь на весь объект, которому принадлежит PotentialMemoryLeaker, тогда все это будет собрано сборщиком мусора. В противном случае вы можете установить для фу значение null или пустой список обработчиков событий foo, написав следующее:
foreach (var handler in AnEvent.GetInvocationList()) AnEvent -= handler;
Конечно, вам понадобится доступ к закрытым членам класса MyObject.
Что ж, вы можете расширить то, что было сделано здесь, чтобы сделать делегатов более безопасными в использовании (без утечек памяти)
Как указано в ответах ниже, отцепить его без сохранения ссылки невозможно. Однако вы можете отключить его сам: stackoverflow.com/questions/1747235/…