Анонимные обработчики событий и удаление - PullRequest
5 голосов
/ 02 марта 2012

У меня довольно короткий вопрос об анонимных обработчиках событий:

Это код, который у меня есть :

public void AddTestControl(Control ctrl)
{
    ctrl.Disposed += (o, e) => { RemoveTestControl(ctrl); };
    ctrl.SomeEvent += _Control_SomeEvent;
}

public void RemoveTestControl(Control ctrl)
{
    ctrl.SomeEvent -= _Control_SomeEvent;
}

Этот код выше, илиследует ли переписать код, чтобы удалить обработчик удаленных событий?Как то так:

public void AddTestControl(Control ctrl)
{
    ctrl.Disposed += _Control_Disposed;
    ctrl.SomeEvent += _Control_SomeEvent;
}

public void RemoveTestControl(Control ctrl)
{
    ctrl.Disposed -= _Control_Disposed;
    ctrl.SomeEvent -= _Control_SomeEvent;
}

Ответы [ 3 ]

8 голосов
/ 02 марта 2012

Как правило, единственная ситуация, когда вам нужно удалить обработчики событий из объекта, чтобы он имел право на сборку мусора, - это когда объект publisher (тот, который определяет событие) живет дольше, чем объект subscriber (объект, содержащий обработчики событий). В такой ситуации GC не сможет освободить подписчика, когда он выходит из области видимости, поскольку на него все еще ссылается издатель.

В этом случае, если предположить, что это WebForms или WinForms, издатель (то есть объект Control), скорее всего, является дочерним объектом подписчика (вероятно, Page или 1011 *), который первым выйдет из области видимости, забрав с собой все связанные с ним объекты. Следовательно, не нужно удалять обработчики событий .

2 голосов
/ 02 марта 2012

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

Затем снова , сколько людей потрудится отписаться от каждого обработчика события, например,приложение WinForms?Ссылки на объекты указывают издателю на подписчиков, а не наоборот, поэтому издателя можно собирать, пока подписчики живут.Он не представляет той же опасности, что и противоположная ситуация, когда долгоживущий издатель (например, статическое событие) может расточительно поддерживать потенциально крупных подписчиков живыми даже после того, как их можно было собрать.

Если вы хотите / должны отказаться от подписки, то требование отменить подписку того же делегата делает анонимные обработчики событий немного болезненными. Reactive Extensions решает эту проблему аккуратно: вместо того, чтобы запоминать делегата, на которого вы подписаны, подписка возвращает IDisposable, который отписывается при утилизации.Перебрасывание всех ваших подписок в CompositeDisposable позволяет вам отписаться от всего одним вызовом Dispose.

1 голос
/ 02 марта 2012

Оба кодеза в порядке, но мне нравится второе, как личные предпочтения. Это читается яснее, чем первый.

Помимо первого кода, есть анонимный лямбда-делегат, который получает текущую ссылку на ctrl. Этот код может вести себя неожиданно в зависимости от ситуации и настроек оптимизации компиляции: независимо от того, является ли вызов встроенным или нет.

Не говоря уже о архитектурной проблеме с кодом: у вас есть ControlOwner и несколько дочерних элементов управления. Я предполагаю, что вы добавляете дочерние элементы управления в ControlOwner во время выполнения, а затем пытаетесь реагировать на их поведение, подписывая ControlOwner на события childControl. Это хорошо для таких событий, как _ButtonClicked и т. Д., Но не подходит для Dispose. Пусть дочерний элемент управления обрабатывает его сам по себе, OwnerControl не должен знать об этом. Не говоря уже о том, что он может не существовать в то время, когда вызывается ChildControl [n] .Dispose.

Короче говоря: * лучше оставить событие dispose на ChildControl в одиночку и очистить все в ChildControl.Dispose * не нужно отписываться от событий. Диспетчер событий проверит, жив ли подписчик.

...