Использование IDisposable для отмены подписки на события - PullRequest
47 голосов
/ 17 января 2009

У меня есть класс, который обрабатывает события из элемента управления WinForms. Основываясь на том, что делает пользователь, я защищаю один экземпляр класса и создаю новый для обработки того же события. Мне нужно сначала отписаться от старого экземпляра события - достаточно просто. Я хотел бы сделать это непатентованным способом, если это возможно, и кажется, что это работа для IDisposable. Однако большая часть документации рекомендует использовать IDisposable только при использовании неуправляемых ресурсов, что здесь не применимо.

Если я реализую IDisposable и откажусь от события в Dispose (), я извращаю его намерение? Должен ли я вместо этого предоставить функцию Unsubscribe () и вызвать ее?


Редактировать: Вот некоторый фиктивный код, который как бы показывает, что я делаю (используя IDisposable). Моя фактическая реализация связана с какой-то проприетарной привязкой данных (длинная история).

class EventListener : IDisposable
{
    private TextBox m_textBox;

    public EventListener(TextBox textBox)
    {
        m_textBox = textBox;
        textBox.TextChanged += new EventHandler(textBox_TextChanged);
    }

    void textBox_TextChanged(object sender, EventArgs e)
    {
        // do something
    }

    public void Dispose()
    {
        m_textBox.TextChanged -= new EventHandler(textBox_TextChanged);
    }
}

class MyClass
{
    EventListener m_eventListener = null;
    TextBox m_textBox = new TextBox();

    void SetEventListener()
    {
        if (m_eventListener != null) m_eventListener.Dispose();
        m_eventListener = new EventListener(m_textBox);
    }
}

В реальном коде класс «EventListener» более вовлечен, и каждый экземпляр уникален. Я использую их в коллекции и создаю / уничтожаю их, когда пользователь щелкает мышью.


Заключение

Я принимаю ответ gbjbaanb , по крайней мере, пока. Я чувствую, что польза от использования знакомого интерфейса перевешивает любые возможные недостатки его использования, когда не используется неуправляемый код (как бы пользователь этого объекта мог это знать?)

Если кто-то не согласен - пожалуйста, напишите / прокомментируйте / отредактируйте. Если можно будет привести более убедительный аргумент против IDisposable, я изменю принятый ответ.

Ответы [ 9 ]

40 голосов
/ 17 января 2009

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

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

Цель IDisposable - улучшить работу вашего кода без необходимости выполнять много ручной работы. Используйте его силу в свою пользу и избавьтесь от искусственной ерунды с «замыслом дизайна».

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

12 голосов
/ 17 января 2009

Мой личный голос должен был бы иметь метод Отписаться, чтобы удалить класс из событий. IDisposable - это шаблон, предназначенный для детерминированного высвобождения неуправляемых ресурсов. В этом случае вы не управляете какими-либо неуправляемыми ресурсами и, следовательно, не должны реализовывать IDisposable.

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

9 голосов
/ 26 октября 2010

Одна вещь, которая беспокоит меня при использовании шаблона IDisposable для отказа от подписки на события, это проблема финализации.

Dispose() функция в IDisposable должна вызываться разработчиком, ОДНАКО, если она не вызывается разработчиком, подразумевается, что GC вызовет эту функцию (по стандарту IDisposable шаблон, по крайней мере). В вашем случае, однако, если вы не позвоните Dispose, никто больше этого не сделает - событие остается, и строгая ссылка удерживает GC от вызова финализатора.

Сам факт, что GC не будет вызывать Dispose () автоматически, мне кажется достаточным, чтобы в этом случае не использовать IDisposable. Возможно, он требует нового специфичного для приложения интерфейса, который говорит, что объект этого типа должен иметь функцию Cleanup , вызываемую GC для удаления.

4 голосов
/ 05 января 2011

Из всего, что я читал об одноразовых расходных материалах, я бы сказал, что они действительно были изобретены главным образом для решения одной проблемы: своевременного освобождения неуправляемых системных ресурсов. Но все же все примеры , которые я обнаружил, не только сосредоточены на теме неуправляемых ресурсов, но также имеют другое общее свойство: Dispose вызывается только для ускорения процесса, который в противном случае произошел бы позже автоматически (GC -> финализатор -> dispose)

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

Таким образом, основное отличие состоит в том, что события каким-то образом создают граф объектов, который не может быть собран, поскольку объект обработки событий внезапно становится ссылкой на сервис, на который вы просто хотели сослаться / использовать. Вы внезапно вынуждены вызвать Dispose - автоматическое удаление невозможно . Таким образом, Dispose получит тонкое иное значение, чем то, которое можно найти во всех примерах, когда вызов Dispose - в грязной теории;) - не нужен, поскольку он будет вызываться автоматически (в некоторый момент времени) ...

Во всяком случае. Поскольку одноразовый шаблон уже довольно сложен (он имеет дело с финализаторами, которые трудно понять правильно, и со многими руководящими принципами / контрактами) и, что более важно, в большинстве пунктов не имеет ничего общего с темой обратной ссылки на событие, я бы сказал, что это будет проще получить это разделение в наших головах, просто не используя эту метафору для чего-то, что можно было бы назвать «unroot from object graph» / «stop» / «off off».

Чего мы хотим добиться, так это отключить / остановить некоторое поведение (отписавшись от события). Было бы неплохо иметь стандартный интерфейс, такой как IStoppable, с методом Stop (), который по контракту просто фокусируется на

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

Давайте назовем единственный интерфейсный метод, который отменяет подписку «Stop ()». Вы бы знали, что остановленный объект находится в приемлемом состоянии, но только остановлен. Возможно, было бы неплохо иметь простое свойство "Остановлено".

Было бы даже целесообразно иметь интерфейс «IRestartable», который наследуется от IStoppable и дополнительно имеет метод «Restart ()», если вы просто хотите приостановить определенное поведение, которое, безусловно, понадобится снова в будущем, или сохранить удаленный объект модели в истории для последующего восстановления отмены.

После всего написанного я должен признаться, что только что видел пример IDisposable где-то здесь: http://msdn.microsoft.com/en-us/library/dd783449%28v=VS.100%29.aspx Но в любом случае, пока я не получу каждую деталь и оригинальную мотивацию IObservable, я бы сказал, что это не лучший пример использования

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

Но, похоже, они на правильном пути. В любом случае: они должны были использовать мой интерфейс "IStoppable";), так как я твердо верю, что есть разница в

  • Утилизировать: «Вы должны вызвать этот метод или что-то еще может утечка , если GC опаздывает» ....

и

  • Стоп: «Вы должны вызвать этот метод для остановить определенное поведение »
4 голосов
/ 05 августа 2009

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

public class DisposableEvent<T> : IDisposable
    {

        EventHandler<EventArgs<T>> Target { get; set; }
        public T Args { get; set; }
        bool fired = false;

        public DisposableEvent(EventHandler<EventArgs<T>> target)
        {
            Target = target;
            Target += new EventHandler<EventArgs<T>>(subscriber);
        }

        public bool Wait(int howLongSeconds)
        {
            DateTime start = DateTime.Now;
            while (!fired && (DateTime.Now - start).TotalSeconds < howLongSeconds)
            {
                Thread.Sleep(100);
            }
            return fired;
        }

        void subscriber(object sender, EventArgs<T> e)
        {
            Args = e.Value;
            fired = true;
        }

        public void Dispose()
        {
            Target -= subscriber;
            Target = null;
        }

    }

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

Class1 class1 = new Class1();
            using (var x = new DisposableEvent<object>(class1.Test))
            {
                if (x.Wait(30))
                {
                    var result = x.Args;
                }
            }

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

3 голосов
/ 17 января 2009

Другой вариант - использовать слабые делегаты или что-то вроде слабые события WPF вместо явной отписки от подписки.

P.S. [OT] Я считаю решение предоставить сильных делегатов единственной самой дорогой ошибкой проектирования платформы .NET.

3 голосов
/ 17 января 2009

Одним из вариантов может быть вообще не отписываться - просто изменить значение подписки. Если обработчик событий можно сделать достаточно умным, чтобы знать, что он должен делать, исходя из контекста, вам не нужно в первую очередь отписываться.

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

3 голосов
/ 17 января 2009

IDisposable твердо о ресурсах и источнике достаточного количества проблем, чтобы не запачкать воду дальше.

Я также голосую за метод отмены подписки на вашем собственном интерфейсе.

1 голос
/ 21 августа 2013

Нет, вы не препятствуете намерению IDisposable. IDisposable предназначен как универсальный способ гарантировать, что, когда вы закончите использовать объект, вы сможете заранее очистить все, что связано с этим объектом. Это могут быть не только неуправляемые ресурсы, но и управляемые ресурсы. А подписка на события - это просто еще один управляемый ресурс!

Подобный сценарий, который часто возникает на практике, заключается в том, что вы реализуете IDisposable для своего типа, исключительно для того, чтобы гарантировать возможность вызова Dispose () для другого управляемого объекта. Это тоже не извращение, это просто аккуратное управление ресурсами!

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