Использование IDisposable для отмены подписки на события

StackOverflow https://stackoverflow.com/questions/452281

Вопрос

У меня есть класс, который обрабатывает события из элемента управления 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, то я изменю принятый ответ.

Это было полезно?

Решение

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

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

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

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

Другие советы

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

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

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

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

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

Я думаю, что одноразовый предназначен для всего, о чем 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 в своих событиях, поскольку это предотвращает передачу их в качестве параметра вспомогательному конструктору, однако, похоже, это не имеет никаких негативных последствий.

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

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

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

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

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

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

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

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

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

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

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

  • Утилизировать:"ты следует вызовите этот метод или что-то в этом роде мог бы утечка если GC случается слишком поздно " ....

и

  • Остановка:"ты должны вызовите этот метод для остановить определенное поведение"

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top