.net-使用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的回答。 我觉得使用熟悉的界面的好处胜于不涉及任何非托管代码的使用它的任何弊端(该对象的用户怎么会知道呢?)。

如果有人不同意-请发表/评论/编辑。 如果可以对IDisposable提出更好的论据,那么我将更改接受的答案。

Jon B asked 2020-02-29T11:46:34Z
9个解决方案
39 votes

是的,去吧。 尽管有些人认为IDisposable仅针对非托管资源实现,但事实并非如此-非托管资源恰好是最大的赢家,也是实现它的最明显原因。 我认为它获得了这个主意,因为人们无法想到使用它的任何其他原因。 它不像终结器,这是一个性能问题,GC很难很好地处理。

在您的dispose方法中放入任何整理代码。 与记住撤消引用无关,它会更加清晰,整洁,并且更有可能防止内存泄漏,并且更容易正确使用该死的视线。

IDisposable的目的是使您的代码更好地工作,而无需进行大量的手工工作。 使用它的功能对您有利,并克服一些人为的“设计意图”废话。

我记得当初.NET刚问世时,很难说服Microsoft进行确定性终结的用途-我们赢得了这场战斗,并说服他们添加了它(即使当时只是一个设计模式),也要使用它!

gbjbaanb answered 2020-02-29T11:46:59Z
13 votes

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

IDisposable可用于管理事件订阅,但可能不应该这样做。 例如,我指出了WPF。 这是一个带有事件和事件处理程序的库。 但是,实际上WPF中没有任何类实现IDisposable。 我认为这表明应该以其他方式管理事件。

JaredPar answered 2020-02-29T11:47:24Z
9 votes

使用Dispose模式取消订阅事件困扰我的一件事是终结问题。

IDisposable中的Dispose函数应该由开发人员调用,但是,如果开发人员未调用它,则可以理解,GC将调用此函数(至少通过标准IDisposable模式)。 但是,就您而言,如果您不致电Dispose,则不会再有其他人-该事件仍然存在,并且强引用阻止GC调用终结器。

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

VitalyB answered 2020-02-29T11:47:54Z
4 votes

我认为一次性处理适用于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关键字,因为那样会阻止将它们作为参数传递给辅助构造函数,但是,这似乎没有任何不良影响。

Jason Coyne answered 2020-02-29T11:48:23Z
4 votes

从我所看到的有关一次性用品的所有文章中,我会说,它们的确主要是为解决一个问题而发明的:及时释放不受管理的系统资源。 但是,我发现的所有示例不仅都集中在非托管资源这一主题上,而且还有另一个共同点:调用Dispose只是为了加速一个过程,否则该过程以后会自动发生(GC-> finalizer-> dispose)

但是,即使您添加了一个可以调用您的Dispose的终结器,也永远不会自动调用从事件中取消订阅的Dispose方法。 (至少只要事件拥有对象存在就不会-如果调用它,您将不会从取消订阅中受益,因为事件拥有对象无论如何也会消失)

因此,主要区别在于事件以某种方式构建了无法收集的对象图,因为事件处理对象突然成为您刚要引用/使用的服务的引用。 您突然被迫致电Dispose-无法进行自动处置。 因此,与在所有不需要Dispose调用(在肮脏的理论中;)的示例中发现的情况相比,Dispose会具有其他微妙的含义,因为它将自动(在某个时间)被调用...

无论如何。由于一次性模式已经很复杂了(很难完成的终结器的交易以及许多指南/合同),更重要的是,在大多数情况下,事件返回引用主题与之无关。 通过不将隐喻用于所谓的“从对象图上取消root” /“停止” /“关闭”这样的事情,更容易使我们与众不同。

我们想要实现的是禁用/停止某些行为(通过取消订阅事件)。最好有一个标准接口,如IStoppable和Stop()方法,而按约定只专注于

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

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

如果您只是想暂停将来肯定会再次需要的某些行为,或者存储已删除的行为,那么拥有从IStoppable继承的接口“ IRestartable”甚至还具有方法“ Restart()”甚至是有意义的。 历史记录中的模型对象,以供以后撤消恢复。

毕竟,我不得不承认只是在这里的某个地方看到了IDisposable的示例:[http://msdn.microsoft.com/zh-cn/library/dd783449%28v=VS.100%29.aspx]但是无论如何,直到我掌握了所有细节和IObservable的原始动机之后,我才会说这不是最佳的使用案例

  • 因为这又是一个非常复杂的系统,所以这里只有一个小问题
  • 整个新系统的动机之一可能是首先消除事件,这将导致与原始问题有关的某种堆栈溢出

但似乎他们走上了正确的道路。 无论如何:他们应该使用我的界面“ IStoppable”;),因为我坚信在

  • 处理:“您应该调用该方法,否则如果GC发生时机可能会泄漏”。

  • 停止:“您必须调用此方法才能停止某些行为”
Sebastian Gregor answered 2020-02-29T11:49:59Z
3 votes

我认为,IDisposable坚定地与资源有关,并且有足够多的问题来避免使问题更加混乱。

我也为您自己的接口上的取消订阅方法投票。

annakata answered 2020-02-29T11:50:24Z
3 votes

一种选择可能是根本不取消订阅-只是更改订阅的含义。 如果可以使事件处理程序变得足够聪明,从而可以根据上下文知道其含义,则无需首先取消订阅。

在您的特定情况下,这可能不是一个好主意-我认为我们确实没有足够的信息-但这值得考虑。

Jon Skeet answered 2020-02-29T11:50:49Z
3 votes

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

附言 [OT]我认为仅向强大的代表提供.NET平台最昂贵的单个设计错误的决定。

answered 2020-02-29T11:51:14Z
1 votes

不,您并没有阻止IDisposable的意图。 IDisposable旨在作为一种万能的方式来确保在使用完对象后可以主动清除与该对象相关的所有内容。 它不必只是非托管资源,它也可以包括托管资源。 事件订阅只是另一个托管资源!

在实践中经常出现的类似情况是,您将在类型上实现IDisposable,纯粹是为了确保可以在另一个托管对象上调用Dispose()。 这也不是一个变态,只是整洁的资源管理!

Tim Lovell-Smith answered 2020-02-29T11:51:39Z
translate from https://stackoverflow.com:/questions/452281/using-idisposable-to-unsubscribe-events