Нужно ли явно удалять обработчики событий в C # - PullRequest
113 голосов
/ 03 февраля 2009

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

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

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

Ответы [ 2 ]

174 голосов
/ 03 февраля 2009

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

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 будет работать по крайней мере столько же, сколько и служба передачи.

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

РЕДАКТИРОВАТЬ: Это ответить на комментарий Джонатана Дикинсона. Во-первых, посмотрите на документы для Delegate.Equals (объект) , которые явно дают поведение равенства.

Во-вторых, вот короткая, но полная программа, показывающая работоспособность отписки:

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; здесь Mono ведет себя немного странно. Рассмотрим это некоторое время):

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
7 голосов
/ 03 февраля 2009

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...