使用IDisposable取消订阅事件

Jon*_*n B 49 .net events idisposable unsubscribe

我有一个类来处理来自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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在实际代码中,"EventListener"类涉及更多,每个实例都具有独特的重要性.我在集合中使用它们,并在用户点击时创建/销毁它们.


结论

我接受gbjbaanb的回答,至少目前如此.我觉得使用熟悉的界面的好处超过了在没有涉及非托管代码的情况下使用它的任何可能的缺点(这个对象的用户怎么会知道?).

如果有人不同意 - 请发帖/评论/编辑.如果可以对IDisposable做出更好的论证,那么我将改变接受的答案.

gbj*_*anb 40

是的,去吧.虽然有些人认为IDisposable仅针对非托管资源实现,但事实并非如此 - 非托管资源恰好是最大的胜利,也是实现它的最明显的理由.我认为它获得了这个想法,因为人们无法想到使用它的任何其他原因.它不像一个性能问题的终结器,并且GC不易处理.

在您的dispose方法中添加任何整理代码.它会更清晰,更清晰,更有可能防止内存泄漏,并且比试图记住取消引用更容易正确使用.

IDisposable的目的是让您的代码更好地工作,而无需您进行大量的手动工作.使用它的力量对你有利,并克服一些人为的"设计意图"废话.

我记得当.NET首次出现时,很难说服微软确定最终确定的有用性 - 我们赢得了战斗并说服他们添加它(即使它当时只是一种设计模式),使用它!

  • 什么合同?没有任何地方说"IDisposable仅用于非托管资源".通常它说"可以用于",但这是非常不同的. (7认同)
  • @Domenic:我同意gbjbaanb.即使文档确实说IDisposable仅用于释放非托管资源,当您将其用于其他清理时,您也不会真正破坏任何硬合同(如前置条件,后置条件和类不变量). (4认同)
  • 我用这种方法找到的问题是你几乎所有的类都实现了IDisposable.您将一个事件处理程序添加到类,因此您在该类上实现IDisposable.然后,您必须使用前一个类在所有类上实现IDisposable,以在结束该类的工作时调用Dispose.很快你会发现自己在一半的课程中使用了IDisposable,这些课程认为不是界面的预期用途. (3认同)
  • 放走听众不就算是清理了吗? (2认同)

Jar*_*Par 13

我的个人投票是使用Unsubscribe方法从事件中删除类.IDisposable是一种用于确定性释放非托管资源的模式.在这种情况下,您不管理任何非托管资源,因此不应实现IDisposable.

IDisposable可用于管理事件订阅,但可能不应该.举个例子,我指向WPF.这是一个充满活动和事件处理程序的图书馆.然而,WPF中几乎没有类实现IDisposable.我认为这表明应该以另一种方式管理事件.

  • WPF几乎没有任何IDisposable控件,因为它使用WeakEvent模式来解决漏洞:http://msdn.microsoft.com/en-us/library/aa970850.aspx (7认同)
  • @gbjbaanb - 这就是我所理解的,尽管这可能会支持JaredPar关于"表明事件应该以另一种方式管理"的观点.我认为其中一种方式可能是WeakEvent模式,另一种方法是自定义IUnsubscribable接口,例如可以模仿IDisposable的使用方式. (2认同)

Vit*_*lyB 9

使用IDisposable模式取消订阅事件困扰我的一件事是Finalization问题.

Dispose()函数in IDisposable应该由开发人员调用,但是如果它没有被开发人员调用,那么可以理解GC将调用这个函数(IDisposable至少通过标准模式).但是,在你的情况下,如果你不打电话,Dispose其他人就不会 - 事件仍然存在且强引用使GC无法调用终结器.

Dispose我看来,()不会自动调用()的事实似乎足以在这种情况下不使用IDisposable.也许它需要一个新的特定于应用程序的接口,该接口说这种类型的对象必须具有一个由GC处理的清理函数.

  • 用我的代码过了一会儿,我倾向于同意你的观点.但是,我仍然担心IDisposable的模糊性,并希望微软更好地处理这个问题. (2认同)

小智 5

另一种选择是使用弱委托或诸如WPF 弱事件之类的东西,而不必显式取消订阅。

PS [OT] 我认为只提供强委托的决定是 .NET 平台最昂贵的设计错误。


Jas*_*yne 5

我认为一次性适用于 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;
        }

    }
Run Code Online (Sandbox Code Playgroud)

这让您可以编写此代码:

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

一个副作用是,您不能在事件上使用 event 关键字,因为这会阻止将它们作为参数传递给辅助构造函数,但是,这似乎没有任何不良影响。