Это необходимо явно удалять обработчики событий в C#


У меня есть класс, который предлагает несколько событий. Этот класс объявляется глобально, но не создается на основе этого глобального объявления-он создается по мере необходимости в методах, которые в нем нуждаются.

каждый раз, когда этот класс необходим в методе, он создается и регистрируются обработчики событий. Необходимо ли явно удалять обработчики событий до того, как метод выйдет из области видимости?

когда метод выходит из области видимости, так идет экземпляра класса. Имеет ли выход обработчиков событий, зарегистрированных в этом экземпляре, который выходит за пределы области, значение объема памяти? (Мне интересно, если обработчик событий не позволяет GC видеть экземпляр класса как больше не упоминается.)

спасибо.

2 111

2 ответа:

в вашем случае все в порядке. Это объект, который опубликовывает события, которые держит цели из обработчиков событий живут. Так что если у меня есть:

publisher.SomeEvent += target.DoSomething;

затем publisher имеет ссылку на target но не наоборот.

в вашем случае издатель будет иметь право на сборку мусора (при условии, что на него нет других ссылок), поэтому тот факт, что у него есть ссылка на цели обработчика событий, является неуместный.

хитрый случай, когда издатель является долгожителем, но подписчики не хотят быть-в это если вам нужно отписаться от обработчиков. Например, предположим, что у вас есть служба передачи данных, которая позволяет подписаться на асинхронные уведомления об изменениях пропускной способности, а объект Службы передачи является долговечным. Если мы сделаем это:

BandwidthUI ui = new BandwidthUI();
transferService.BandwidthChanged += ui.HandleBandwidthChange;
// Suppose this blocks until the transfer is complete
transferService.Transfer(source, destination);
// We now have to unsusbcribe from the event
transferService.BandwidthChanged -= ui.HandleBandwidthChange;

(вы на самом деле хотите использовать блок finally, чтобы убедиться, что вы не утечка события обработчик.) Если мы не отписались, то то BandwidthUI будет жить как минимум до тех пор, как трансфер.

лично я редко сталкиваюсь с этим - обычно, если я подписываюсь на событие, цель этого события живет по крайней мере до тех пор, пока издатель - форма будет длиться до тех пор, пока кнопка, которая находится на нем, например. Стоит знать об этой потенциальной проблеме, но я думаю, что некоторые люди беспокоятся об этом, когда им это не нужно, потому что они не знают, как обойти ссылки идти.

EDIT: это ответ на комментарий Джонатана Дикинсона. Во-первых, посмотрите на документы для делегат.Равно (объект) которые явно дают поведение равенства.

во-вторых, вот короткая, но полная программа, чтобы показать отписку работает:

using System;

public class Publisher
{
    public event EventHandler Foo;

    public void RaiseFoo()
    {
        Console.WriteLine("Raising Foo");
        EventHandler handler = Foo;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
        else
        {
            Console.WriteLine("No handlers");
        }
    }
}

public class Subscriber
{
    public void FooHandler(object sender, EventArgs e)
    {
        Console.WriteLine("Subscriber.FooHandler()");
    }
}

public class Test
{
    static void Main()
    {
         Publisher publisher = new Publisher();
         Subscriber subscriber = new Subscriber();
         publisher.Foo += subscriber.FooHandler;
         publisher.RaiseFoo();
         publisher.Foo -= subscriber.FooHandler;
         publisher.RaiseFoo();
    }
}

результаты:

Raising Foo
Subscriber.FooHandler()
Raising Foo
No handlers

(проверено на Mono и .NET 3. 5SP1.)

далее редактировать:

это должно доказать, что издатель событий может быть собран, пока есть ссылки на подписчика.

using System;

public class Publisher
{
    ~Publisher()
    {
        Console.WriteLine("~Publisher");
        Console.WriteLine("Foo==null ? {0}", Foo == null);
    }

    public event EventHandler Foo;
}

public class Subscriber
{
    ~Subscriber()
    {
        Console.WriteLine("~Subscriber");
    }

    public void FooHandler(object sender, EventArgs e) {}
}

public class Test
{
    static void Main()
    {
         Publisher publisher = new Publisher();
         Subscriber subscriber = new Subscriber();
         publisher.Foo += subscriber.FooHandler;

         Console.WriteLine("No more refs to publisher, "
             + "but subscriber is alive");
         GC.Collect();
         GC.WaitForPendingFinalizers();         

         Console.WriteLine("End of Main method. Subscriber is about to "
             + "become eligible for collection");
         GC.KeepAlive(subscriber);
    }
}

результаты (в .NET 3. 5SP1; моно, кажется, ведет себя немного странно здесь. Посмотрим на это некоторое время):

No more refs to publisher, but subscriber is alive
~Publisher
Foo==null ? False
End of Main method. Subscriber is about to become eligible for collection
~Subscriber

в вашем случае, вы прекрасны. Я изначально прочитал Ваш вопрос назад, что a абонент выходил за рамки, а не издатель. Если издатель событий выходит за пределы области действия, то ссылки к абоненту (не сам абонент, конечно!) и не нужно явно удалить их.

мой оригинальный ответ ниже, о том, что произойдет, если вы создадите событие абонент и пусть это выйдите из области действия без отписки. Это не относится к вашему вопросу, но я оставлю это для истории.

Если класс все еще зарегистрирован через обработчики событий, то он все еще доступен. Это все еще живой объект. GC, следующий за графом событий, найдет его связанным. Да, вы хотите явно удалить обработчики событий.

только потому, что объект находится вне области его первоначального распределения, не означает, что он является кандидатом на GC. Пока живая ссылка остается, она живая.