Должен ли я отказаться от подписки на анонимные обработчики событий локальных переменных?

Если у меня есть код, который выглядит примерно так:

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;