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

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

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?

Как указано в ответах ниже, отцепить его без сохранения ссылки невозможно. Однако вы можете отключить его сам: stackoverflow.com/questions/1747235/…

Benjol 17.11.2009 10:57
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
20
1
6 279
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Ответ принят как подходящий

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

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.

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

Другие вопросы по теме