Finalizer和IDisposable

11

根据文档(MSDN:链接)显示,当实现finalizer时,应该使用IDisposable模式。

但是,如果你实现了IDisposable(以提供一种确定性的方式来处理对象),并且没有未管理的资源需要清理,那么你是否需要实现finalizer呢?

在我看来,如果该类仅具有托管资源,并且如果您不调用Dispose,则托管资源将会被GC自动清除,因此无需实现finalizer。我的看法正确吗?

另外,如果我正在使用Dispose方法来清除事件处理程序,由于GC不会自动调用Dispose,所以我应该实现Finalizer,以确保事件处理程序被取消关联吗?


根据所有的评论,我已经在这个问题上写了一篇博客文章:http://blog.aggregatedintelligence.com/2010/10/idisposable-and-finalizers.html - Raj Rao
在 .Net 2.0 引入 SafeHandle 类之前,关于终结器的许多信息都已经写好。遵循“Dispose 模式”可能是个好主意,因为未来维护代码的人可能会期望这样做,但很少有类应该有终结器。我无法想象出任何一个从非平凡基类派生的类都需要终结器的情况,除非使用某些奇怪的魔法,否则终结器不会对事件起到任何有用的作用;悬空事件将阻止终结器运行,除非或者直到情况使得它们的清理变得无关紧要。 - supercat
6个回答

12
不需要实现finalizer(终结器),如果您的类实现了IDisposable接口(如果您正确地实现了该模式,并且只有托管资源需要处理)。如果您实现了finalizer,它实际上会影响对象的生命周期,因为具有finalizer的对象会添加到GC的终结队列中,并且可能比其需要的时间更长生存-如果您的对象很大,则这可能是个问题。请注意,如果您的类不包含非托管资源,则无需实现IDisposable接口或终结器。

@Rajah:我们的答案并不矛盾。然而,我是正确的;请参见http://msdn.microsoft.com/en-us/library/ms244737%28v=VS.100%29.aspx和http://msdn.microsoft.com/en-us/magazine/cc163324.aspx。 - SLaks
@Rajah:答案并不矛盾。我回答了你的问题:“如果你实现了IDisposable,但没有任何未托管的资源需要清理,那么你需要实现finalizer吗?”答案是不需要。 - adrianbanks
根据这个问题的所有评论,我已经写了一篇文章 http://blog.aggregatedintelligence.com/2010/10/idisposable-and-finalizers.html。 - Raj Rao

11

除非您有不受管控的资源,否则不应添加终结器。

拥有托管可释放资源但没有不受管控的资源的类应实现完整的Dispose模式,但不应该有终结器。

如果这个类不是sealed,它应该在其Dispose()方法中调用GC.SuppressFinalize(this),以防继承类添加了一个终结器。


1
如果继承的类还添加了一个非托管资源呢? - H H
2
@Henk:嗯?你在说什么?SuppressFinalize是由非虚拟的Dispose()方法调用的,应该在任何情况下都要调用它。如果派生类添加了非托管资源,则应在Dispose(bool)中清理它,并添加一个调用Dispose(false)的终结器。如果调用了正常的Dispose方法,则会清除非托管资源,因此应该抑制终结器。 - SLaks
1
我认为在可终结的类中,无论是否密封,都应该在Dispose()中调用GC.SuppressFinalize(this)。话虽如此,我仍然会将其密封,并避免使用Dispose(bool)反模式。 - Jon Hanna
1
@Jon:如果类是密封的并且没有终结器,那么调用SuppressFinalize就没有意义了。 - SLaks
一个派生类不应该提供一个finalizer(C#:“析构函数”)。相反,它应该依赖于其基类提供的finalizer。从IDisposable接口的MSDN文档中,在DisposeExample示例中,对析构函数的注释如下:// Do not provide destructors in types derived from this class. - DavidRR
显示剩余12条评论

2
  1. 如果您的对象持有一个持有未管理资源的对象,则应实现IDisposable,以便在Dispose中调用其Dispose,但您不需要一个finalizer,因为它的finalizer将处理该问题。

  2. 实际上,在finalizer中尝试使用可终止成员来执行任何操作是很危险的,因为finalizer的运行顺序不是确定性的,所以如果尝试这样做,可能会出现一些严重的错误。

  3. 通常情况下,最好让一个类只持有1个或0个未管理的资源。如果它有1个未管理的资源,它应该具有尽可能少的其他状态来处理它(即没有其他可处理的成员)。SafeHandle是处理此问题的好方法。如果一个类需要处理多个未管理的资源,则应通过这些处理程序类来处理这些资源。然后,finalizer和IDisposable变得容易;您可以在两者中都处理唯一的未管理资源(如果调用了dispose,则抑制finalizer),或者您只需要IDisposable。

由于直接处理未管理的资源相对较少,因此您可能永远不必编写finalizer(我认为我在真正的代码中只做过一次)。因为明智的人在处理未管理的资源的类中不做太多其他事情,所以整个Dispose(bool)过程也是不必要的。


0
如果您只有托管资源,则根本不需要实现IDisposable。 IDisposable 旨在清除超出 GC 范围的内容,例如本机句柄、数据库连接等。
如果您的控件包含实现 IDisposable 的控件,并且它们必须释放本机资源,则仍需要实现 IDisposable 模式,并让您的子控件有机会进行处理。
在终结器中调用 Dispose() 的原因是作为最后的手段,如果对象未被正确处理,GC 将作为最后一次努力来处理它。

你忽略了一个情况,即你的类持有对实现IDisposable接口的托管资源的引用。那么你应该编写一个Dispose方法,在其中调用这些对象的Dispose方法。 - Isak Savo
@Isak 我在第二段中解决了这个问题。 - Matt Greer
好的,我现在明白了。不过你的第一段话有点误导人——我认为实现IDisposable接口的类应该是托管的。 - Isak Savo

0

是的,如果您只有托管资源,当垃圾回收发生时(并且没有任何指向它们的活动引用),它们将由GC清理。

但在这种情况下,为什么需要在类型上实现IDisposable?我的意思是,您似乎认为在您的情况下,不处理对象并不是一个大问题,那么为什么有人要处理它们呢?

您还应该注意,在使用终结器时会有性能损失:任何具有终结器的对象都将逃脱第一次GC检查,如果这些对象的寿命很短,则会大大降低GC效率。

在应该清理对象的第一次垃圾回收期间,它不会被清理,以便执行终结器。然后,GC将认为该对象已经存在很长时间,即使它应该已经被清理。


0

我从未有过实现终结器(finalizer)的需要。正如你所知,它为对象在垃圾回收之前提供了执行任何必要操作的机会。所有资源应该在dispose方法中被释放。


1
非托管资源必须由终结器(和Dispose;参见模式)清理。 - SLaks
除了 SafeHandle 之外,有什么理由让任何新编写的类持有非托管资源?显然,那些早于 SafeHandle 的类必须这样做,但是对于新编写的类来说,是否存在这样的理由呢? - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接