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