【问题标题】:Finalizer and IDisposable终结器和 IDisposable
【发布时间】:2011-04-22 10:05:08
【问题描述】:

根据文档(MSDN:link),很明显在实现终结器时应该使用 IDisposable 模式。

但是,如果您实现了 IDisposable(以便提供一种确定性的方式来处理对象),并且您没有任何非托管资源需要清理,您是否需要实现终结器?

在我看来,如果该类只有托管资源并且您不调用 Dispose,则托管资源将自动被 GC 清理,因此无需实现终结器。我错了吗?

另外,如果我使用我的 Dispose 方法来清理事件处理程序会怎样。由于 Dispose 不会自动被 GC 调用,我是否应该实现一个终结器,以确保事件处理程序不连线?

【问题讨论】:

  • 综合所有的cmets,我写了一篇关于这个问题的博文:blog.aggregatedintelligence.com/2010/10/…
  • 许多关于终结器的信息是在 SafeHandle 类添加到 .Net 2.0 之前编写的。遵循 Dispose 模式可能会很好,因为将来维护您的代码的人可能会期望它,但是很少有类应该有终结器,而且我想不出 any 类派生自非平凡的基础应该有一个终结器。除非使用一些奥术魔法,否则终结器不会对事件做任何有用的事情;悬空事件将阻止终结器运行,除非或直到环境使它们的清理没有意义。

标签: c# dispose idisposable finalizer garbage-collection


【解决方案1】:

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

拥有托管一次性资源但不拥有非托管资源的类应实现完整的Dispose 模式,但没有终结器。

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

【讨论】:

  • 如果继承的类也添加了非托管资源怎么办?
  • @Henk:这就是它添加终结器的原因。 (并覆盖Dispose(bool)
  • @Henk:嗯?你在说什么? SuppressFinalize 由非虚拟的Dispose() 方法调用,在任何情况下都应该调用。如果派生类添加了非托管资源,它应该在Dispose(bool) 中清理它,并添加一个调用Dispose(false) 的终结器。如果调用普通的Dispose 方法,则非托管资源会被清理,因此应该抑制终结器。
  • 我会说你应该在 Dispose() 中调用 GC.SuppressFinalize(this) 在一个可终结的类中,无论是否密封。也就是说,无论如何我都会将其密封,并且不会遭受 Dispose(bool) 反模式。
  • @Jon: 如果类是密封的并且没有终结器,那么调用SuppressFinalize 是没有意义的。
【解决方案2】:
  1. 不,你是对的,如果你的对象拥有一个拥有非托管资源的对象,那么你应该实现 IDisposable,这样你就可以在你的 Dispose 上调用它的 Dispose,但你不需要终结器作为它的终结器会处理这件事。

  2. 确实,尝试对终结器中的可终结成员执行任何操作是令人担忧的,因为终结器将运行的顺序不确定,因此如果您尝试这样做,可能会遇到一些讨厌的错误。

  3. 通常,让一个类拥有 1 个或 0 个非托管资源会更好。如果它有 1 个非托管资源,它应该有尽可能少的其他状态来处理它(即没有其他可支配成员)。 SafeHandle 是处理这个问题的好方法。如果一个类需要处理几个非托管资源,它应该通过这些处理程序类来处理所述资源。然后终结器和 IDisposable 变得容易;要么你有唯一的非托管资源来处理(如果调用 dispose 则抑制终结器)或者你只需​​要 IDisposable。

因为必须直接处理非托管资源相对较少,所以您可能永远不必编写终结器(我想我曾经这样做过一次,在实际代码中)。因为明智的人不会在处理非托管资源的类中做很多其他事情,所以整个 Dispose(bool) 也是不必要的。

【讨论】:

    【解决方案3】:

    我从来不需要实现终结器。如您所知,它使对象有机会在 GC 之前执行所需的任何操作。所有资源都应该在 dispose 方法中释放

    【讨论】:

    • 非托管资源必须由终结器清理(和 Dispose;参见模式)
    • 除了 SafeHandle 之外的任何新创建的类都存在哪些原因来保存非托管资源?显然,早于 SafeHandle 的类必须这样做,但是新创建的类是否可以这样做?
    【解决方案4】:

    不,如果您有一个实现 IDisposable 的类(也就是说,如果您正确实现了该模式,并且您只有托管资源要处置),则不需要实现终结器。

    (如果这样做,它实际上会影响对象的生命周期,因为带有终结器的对象会被添加到 GC 中的终结队列中,并且可以比它们需要的寿命更长 - 如果你的对象很大。)

    【讨论】:

    【解决方案5】:

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

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

    您还应该注意,在使用终结器时,垃圾回收会降低性能:任何具有终结器的对象都将逃脱第一次 GC 传递,如果这些对象的生命周期很短,这将大大降低 GC 效率。

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

    【讨论】:

      【解决方案6】:

      如果您只有托管资源,那么您根本不需要实现 IDisposable。 IDisposable 旨在清理 GC 域之外的东西,例如本机句柄、数据库连接等。

      如果您的控件包含实现 IDisposable 的控件并且它们必须释放本机资源,那么您仍然需要实现 IDisposable 模式并让您的子控件有机会释放。

      在终结器中调用 Dispose() 的原因是作为最后的手段,如果对象没有正确释放,GC 将作为最后的努力去做。

      【讨论】:

      • 您错过了您的类包含对实现 IDisposable 的托管资源的引用的情况。那么你应该有一个 Dispose 对这些对象调用 Dispose
      • @Isak 我在第二段中提到过。
      • 好的,我现在明白了。不过,您的第一段有点误导 - 我会考虑管理一个实现 Idisposable 的类。
      猜你喜欢
      • 1970-01-01
      • 2011-02-07
      • 2013-07-30
      • 2011-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-18
      • 1970-01-01
      相关资源
      最近更新 更多