Должен ли я отказаться от подписки на анонимные обработчики событий локальных переменных?
Если у меня есть код, который выглядит примерно так:
public void Foo()
{
Bar bar = new Bar();
bar.SomeEvent += (sender, e) =>
{
//Do something here
};
bar.DoSomeOtherThingAndRaiseSomeEvent();
}
Будет ли собрано bar
, когда метод исчерпает область действия, или мне придется вручную отказаться от подписки на событие, чтобы предотвратить утечку памяти из-за ссылки на SomeEvent
?
Ответы
Ответ 1
Ваша ситуация в порядке; абонент события не будет препятствовать сбору издателя, но может произойти обратное.
Например,
class Foo
{
public event EventHandler FooEvent;
void LeakMemory()
{
Bar bar = new Bar();
bar.AttachEvent(this);
}
}
class Bar
{
void AttachEvent(Foo foo)
{
foo.FooEvent += (sender, e) => { };
}
}
В этом случае экземпляр Bar
, созданный в LeakMemory
, не может быть собран до тех пор, пока
- Анонимный метод, представленный лямбдой, удаляется из
FooEvent
списка вызовов
- Пример Foo, к которому он прикреплен, может быть собрано
Это связано с тем, что событие (которое является всего лишь синтаксическим сахаром над обычным экземпляром delegate
) содержит список делегатов, вызываемых при его вызове, и каждый из этих делегатов имеет, в свою очередь, ссылку на объект что он прикреплен к (в данном случае экземпляру Bar
).
Обратите внимание, что мы говорим только о приемлемости коллекции. Просто потому, что он имеет право не говорить ничего о том, когда (или даже, действительно, если) он будет собран, просто, что это может быть.
Ответ 2
Ну, объект bar
ссылается не будет автоматически собирать мусор немедленно... это просто, что переменная bar
не помешает собирать мусор.
Обработчик событий не будет препятствовать тому, чтобы экземпляр bar
был собран в виде мусора, хотя проблема "нормальная" заключается в том, что обработчик события не позволяет собирателю сбора данных собирать мусор (если он использует метод экземпляра или захватывает "this" в анонимной функции). Обычно это не влияет на публикацию сборщика мусора. Просто помните, что издателю необходимо сохранить ссылку на всех подписчиков - абоненту не нужно запоминать, на что он подписан, если он явно не хочет отменить подписку или использовать какой-либо другой элемент позже.
Предполагая, что ничего другого не поддерживает ваш экземпляр bar
, ваш код должен быть в порядке.
Ответ 3
Вышеуказанные ответы верны; Я просто хотел сделать заметку. Анонимные делегаты, используемые в качестве обработчиков, могут быть отписаны только в том случае, если вы сохраните другую ссылку на делегат/лямбда. Это связано с тем, что lambdas являются "функциональными литералами", вроде как строковые литералы, однако в отличие от строк они НЕ сравниваются семантически при определении равенства:
public event EventHandler MyEvent;
...
//adds a reference to this named method in the context of the current instance
MyEvent += Foo;
//Adds a reference to this anonymous function literal to MyEvent
MyEvent += (s,e) => Bar();
...
//The named method of the current instance will be the same reference
//as the named method.
MyEvent -= Foo;
//HOWEVER, even though this lambda is semantically equal to the anonymous handler,
//it is a different function literal and therefore a different reference,
//which will not match the anonymous handler.
MyEvent -= (s,e) => Bar();
var hasNoHandlers = MyEvent == null; //false
//To successfully unsubscribe a lambda, you have to keep a reference handy:
EventHandler myHandler = (s,e) => Bar();
MyEvent += myHandler;
...
//the variable holds the same reference we added to the event earlier,
//so THIS call will remove the handler.
MyEvent -= myHandler;