Скажем, у нас есть следующий метод:
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, то все это будет собирать мусор. В противном случае вы можете установить для foo значение null или пустой список обработчиков событий foo, написав следующее:
foreach (var handler in AnEvent.GetInvocationList()) AnEvent -= handler;
Конечно, вам нужен доступ к закрытым членам класса MyObject .
Что ж, вы можете расширить то, что было сделано здесь, чтобы сделать делегатов более безопасными (без утечек памяти)