我有一个处理来自 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的是仅适用于非托管资源实现的,这种情况并非如此 - 非托管资源恰好是最大的胜利,并实现它最明显的原因。我认为它收购了这个想法,因为人们想不出任何其他原因使用它。它不喜欢finaliser这是一个性能问题,不容易为GC处理好。

把任何整齐式代码在dispose方法。这将是更清晰,更清洁,更显著可能防止内存泄漏和一个该死的视线更容易比试图记住未做你的引用正确使用。

IDisposable接口的目的是让你的代码更好地工作,您无需做大量体力劳动的。使用它的力量在你的青睐,并得到了一些人为的“设计意图”废话。

我记得那是非常困难说服确定性终结的有用的微软早在.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;
                }
            }

一个副作用,您不能使用该事件的关键字在你的事件,因为这可以防止将它们作为参数传递给助手的构造函数,但是,似乎没有任何不良影响。

从我读到的有关一次性用品的所有内容来看,我认为它们实际上主要是为了解决一个问题而发明的:及时释放非托管系统资源。但仍然 所有例子 我发现它们不仅关注非托管资源的主题,而且还有另一个共同的属性:调用 Dispose 只是为了加快进程,否则稍后会自动发生 (GC -> 终结器 -> 处置)

然而,调用取消订阅事件的 dispose 方法永远不会自动发生,即使您添加一个调用您的 dispose 的终结器也是如此。(至少只要事件拥有对象存在 - 并且如果调用它,您将不会从取消订阅中受益,因为事件拥有对象也将消失)

因此,主要区别在于,事件以某种方式构建了一个无法收集的对象图,因为事件处理对象突然变成了您只想引用/使用的服务的引用。你突然是 被迫 调用 Dispose - 否 自动的 处置是可能的。因此,Dispose 会获得一种微妙的其他含义,而不是在所有示例中发现的含义,在这些示例中,Dispose 调用(在肮脏的理论中;)是不必要的,因为它会被自动调用(在某些时候)...

反正。由于一次性模式已经相当复杂(处理很难正确执行的终结器以及许多指南/合同),更重要的是在大多数情况下与事件反向引用主题无关,我想说它是只要不使用可以称为“从对象图中取消根”/“停止”/“关闭”的隐喻,就可以更容易地在我们的头脑中将其分开。

我们想要实现的是禁用/停止某些行为(通过取消订阅事件)。最好有一个像 Istoppable 这样带有 Stop() 方法的标准接口,根据合同,它只专注于

  • 让对象(+所有它自己的可停止对象)与任何不是它自己创建的对象的事件断开连接
  • 这样它就不会再以隐式事件样式的方式调用(因此可以被视为已停止)
  • 一旦对该对象的任何传统引用消失,就可以收集

让我们调用执行取消订阅的唯一接口方法“Stop()”。您会知道停止的对象处于可接受的状态,但只是停止了。也许一个简单的属性“Stopped”也是一个不错的选择。

如果您只是想暂停将来肯定会再次需要的某个行为,或者存储已删除的行为,那么拥有一个继承自 Istoppable 的接口“IRestartable”甚至还有一个方法“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