Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

Ответ 4

Я думаю, что одноразовый - это все, что 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;
                }
            }

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

Ответ 5

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

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

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

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

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

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

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

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

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

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

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

  • Dispose: "вы должны вызвать этот метод или что-то может просочиться, если GC произойдет с опозданием"....

и

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

Ответ 6

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

Я тоже голосовал за метод Unsubscribe на вашем собственном интерфейсе.

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

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

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