使用IDisposable来取消订阅事件

我有一个类来处理来自WinForms控件的事件。 基于用户在做什么,我正在引用该类的一个实例,并创build一个新的来处理同一个事件。 我需要首先从事件中退订旧实例 – 很简单。 如果可能的话,我想以非专有的方式做到这一点,这似乎是一个IDisposable的工作。 但是,大多数文档仅在使用非托pipe资源时才推荐使用IDisposable,这在此不适用。

如果我在Dispose()中实现了IDisposable和取消订阅这个事件,我是否在歪曲它的意图? 我应该提供一个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”类更多地涉及到,每个实例都是独一无二的。 我在集合中使用它们,并在用户点击时创build/销毁它们。


结论

我接受gbjbaanb的答案 ,至less现在。 我觉得使用一个熟悉的界面的好处胜过在不涉及非托pipe代码的情况下使用它的任何可能的缺点(这个对象的用户怎么知道的?)。

如果有人不同意 – 请发表/评论/编辑。 如果可以对IDisposable进行更好的论证,那么我会改变接受的答案。

是的,去做吧。 尽pipe有人认为IDisposable只是为非托pipe资源而实施,但情况并非如此 – 非托pipe资源恰好是最大的胜利,也是最明显的原因。 我认为它获得了这个想法,因为人们不会想到任何其他的理由来使用它。 它不像一个性能问题的终结者,而且GC不容易处理。

将任何整理代码放在你的dispose方法中。 它会更清晰,更清洁,更有可能防止内存泄漏,而且该视觉更容易正确使用,而不是试图记住取消引用。

IDisposable的意图是使您的代码更好地工作,而无需进行大量的手动工作。 利用自己的力量,克服一些人为的“devise意图”的废话。

我记得当.NET第一次出现时说服微软确定性定稿的有用性已经很困难 – 我们赢得了战斗并说服他们添加它(即使它当时只是一种devise模式),使用它!

我个人的投票将是有一个取消订阅方法,以便从事件中删除类。 IDisposable是一个旨在确定性释放非托pipe资源的模式。 在这种情况下,您不pipe理任何非托pipe资源,因此不应执行IDisposable。

IDisposable可以用来pipe理事件订阅,但可能不应该。 举个例子,我把你指向WPF。 这是一个充满事件和事件处理程序的图书馆。 然而事实上WPF中没有任何类实现IDisposable。 我会以此作为表明事件应该以另一种方式进行pipe理的指示。

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

如果开发人员没有调用Dispose()函数,开发人员应该调用Dispose()函数,但应该明白,GC会调用这个函数(至less使用标准的IDisposable模式)。 然而,在你的情况下,如果你不调用Dispose其他人都不会 – 事件依然存在,并且强调的GC引用终结器。

事实上, Dispose ()不会被GC自动调用,在我看来,在这种情况下我不会使用IDisposable。 也许它需要一个新的应用程序特定的接口,它说这种types的对象必须有一个被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关键字,因为这样会阻止把它们作为parameter passing给helper构造函数,但是,这似乎没有任何不良影响。

从所有我读到的关于一次性用品的问题,我会争辩说,他们主要是为了解决一个问题而发明的:及时释放不受pipe理的系统资源。 但是我发现的所有例子不仅仅关注非托pipe资源的主题,还有另外一个共同的特性: Dispose被调用只是为了加快一个进程,否则这个进程会自动发生 (GC ​​- > finalizer – >处置)

调用一个从事件中取消订阅的dispose方法将永远不会自动发生,即使你要添加一个可以调用你的dispose的终结器。 (至less只要事件拥有对象存在 – 如果它被称为你不会受益于取消订阅,因为事件拥有对象也将消失)

所以主要的区别在于事件以某种方式构build了一个无法收集的对象图,因为事件处理对象突然变成了您想引用/使用的服务的引用。 你突然被迫打电话处理 – 不能自动处理。 因此,Dispose会得到一个微妙的意义,而不是在所有的Dispose调用 – 脏理论的例子中find的意思),因为它会被自动调用(在某个时候)。

无论如何。 由于一次性模式已经非常复杂(涉及难以获得正确的定型器和许多指导方针/合同),更重要的是,在大多数情况下,与事件反向参考主题无关,我会说这将是更容易把我们脑海中分离出来的东西,只是不使用这个隐喻来称为“从对象图解除”/“停止”/“closures”的东西。

我们想要实现的是禁用/停止某些行为(通过取消订阅事件)。 有一个标准的接口像IStoppable和Stop()方法是很好的,契约只是集中在这个方法上

  • 获取对象(+它自己的可停止对象)与它自己没有创build的任何对象的事件断开连接
  • 这样它就不会再以隐式事件的方式被调用(因此可以被认为是停止的)
  • 可以尽快收集任何传统的引用到该对象上去

让我们调用唯一的取消订阅“Stop()”的接口方法。 您会知道停止的对象处于可接受的状态,但只能停止。 也许一个简单的财产“停止”也将是一件好事。

如果你只是想暂停一些肯定会在未来再次需要的行为,或者存储一个被删除的对象,那么从IStoppableinheritance一个“IRestartable”接口也是有意义的,另外还有一个方法“Restart模型对象在以后的撤销恢复历史。

毕竟写作,我不得不承认,刚刚看到一个IDisposable的例子在这里: http : //msdn.microsoft.com/en-us/library/dd783449%28v=VS.100%29.aspx但无论如何,直到我得到IObservable的每个细节和原始动机我会说这不是最好的用例

  • 因为它又是一个非常复杂的系统,我们在这里只有一个小问题
  • 也许是整个新系统摆脱事件的动机之一,这将导致一个关于原始问题的堆栈溢出

但似乎他们正在走上正轨。 无论如何:他们应该使用我的界面“IStoppable”;)因为我坚信有一个区别

  • 处置:“你应该调用该方法,或者如果 GC发生迟到, 可能会发生泄漏”….

  • 停止:“你必须调用这个方法来停止某种行为

我认为,一次性使用固定资源,是足够的问题的来源,不要浑水。

我也在自己的界面上投票取消订阅方法。

一种select可能不是取消订阅 – 只是为了改变订阅的含义。 如果事件处理程序可以做得足够聪明,可以根据上下文知道它是什么意思,那么不需要首先取消订阅。

对于您的具体情况,这可能也可能不是一个好主意 – 我认为我们没有真正获得足够的信息 – 但值得考虑。

另一个select是使用弱 代理或类似WPF的弱事件 ,而不是必须明确退订。

PS [OT]我认为决定只给.NET代理提供强大的代表最昂贵的devise错误。

不,你没有扭曲IDisposable的意图。 IDisposable的目的是作为一种多用途的方式来确保当你完成使用一个对象时,你可以主动清理与该对象有关的所有东西。 它不一定只有非托pipe资源,它也可以包含托pipe资源。 而事件订阅只是另一个托pipe资源!

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