【问题标题】:Correct way of implementing Finalize and Dispose(When parent class implements IDisposable)正确实现 Finalize 和 Dispose 的方法(当父类实现 IDisposable 时)
【发布时间】:2011-05-25 06:06:03
【问题描述】:

我在我的类中实现了 Finalize 和 Dispose,我在我的父类中实现了 IDisposable 并在我的子类中覆盖了 Dispose(bool) 重载。我不确定

  1. 是否使用重复的 isDisposed 变量(因为它已经存在于基类中)?
  2. 是否也在子类中实现终结器?

这两件事都在此处给出的示例中完成 -

http://guides.brucejmack.biz/CodeRules/FxCop/Docs/Rules/Usage/DisposeMethodsShouldCallBaseClassDispose.html

而这篇 MSDN 文章中的示例没有这两个 - http://msdn.microsoft.com/en-us/library/b1yfkh5e.aspx

而 MSDN 中的这个例子并不完整 - http://msdn.microsoft.com/en-us/library/ms182330.aspx

【问题讨论】:

  • C# Finalize/Dispose pattern 的可能副本
  • @Øyvind Bråthen:它是相关的,但在继承的情况下没有讨论这种模式。
  • 顺便说一句,我注意到这个问题被 wpf 标签标记,但是基于 WPF 的控件通常不需要实现 IDisposable,因为它们本身并不拥有资源,例如作为 Windows 窗体控件的专用 HWND做。
  • @jpierson:我同意,但我在我的 WPF 应用程序中实现了这个,所以标记了它,以防 WPF 有特定/新的东西。
  • 我不确定为什么这个问题会获得“关闭”选票!我仍然不清楚这两种方法中哪一种是正确的,这里给出了相互矛盾的答案,而且没有一个清楚地提到每种方法的优缺点?如果您能在给出关闭投票后发表评论,我将不胜感激。

标签: c# .net wpf idisposable finalizer


【解决方案1】:

终结器很少有用。您链接到的文档并非完全有帮助 - 它提供了以下相当循环的建议:

仅在对象上实现 Finalize 需要最终确定的

这是提出问题的一个很好的例子,但它不是很有帮助。

在实践中,绝大多数情况下您都不需要终结器。 (.NET 开发人员必须经历的学习曲线之一是发现在他们认为需要终结器的大多数地方,他们不需要。)您已将其标记为(除其他外)一个 WPF 问题,并且我会说在 UI 对象上放置终结器几乎总是一个错误。 (因此,即使您处于需要终结器的异常情况之一,该工作也不属于与 WPF 相关的代码附近的任何地方。)

对于大多数情况下,终结器看起来可能有用,但事实证明并非如此,因为当你的终结器运行时,它已经为时已晚,无法做任何有用的事情.

例如,尝试对对象引用的任何对象执行任何操作通常是一个坏主意,因为在终结器运行时,这些对象可能已经终结。 (.NET 不保证终结器的运行顺序,因此您根本无法知道您引用的对象是否已经终结。)在终结器已经完成的对象上调用方法是个坏主意已运行。

如果您有某种方式知道某个对象肯定还没有被终结,那么使用它是安全的,但这是一种非常不寻常的情况。(......除非有问题的对象没有终结器,并且本身不使用可终结的资源。但在这种情况下,当你自己的对象消失时,它可能不是你真正需要做任何事情的对象。)

终结器似乎有用的主要情况是互操作:例如,假设您正在使用 P/Invoke 调用一些非托管 API,并且该 API 会返回一个句柄。也许您需要调用其他一些 API 来关闭该句柄。由于这都是非托管的东西,.NET GC 不知道这些句柄是什么,你的工作是确保它们被清理,此时终结器是合理的......除了在实践中,它几乎总是最好的在该场景中使用SafeHandle

在实践中,我发现自己使用终结器的唯一地方是 a) 旨在调查 GC 功能的实验,以及 b) 旨在发现特定对象在系统中的使用方式的诊断代码。两种代码都不应最终投入生产。

所以你是否需要“在子类中也实现终结器”的答案是:如果你需要问,那么答案是否定的。

至于是否复制标志......其他答案在这里提供了相互矛盾的建议。要点是 1)您确实需要调用基础 Dispose 和 2)您的 Dispose 需要是幂等的。 (即,它是否被调用一次、两次、5 次、100 次都没有关系——如果它被多次调用,它不应该抱怨。)你可以随意实现它——布尔标志是一种方法,但我经常发现在我的 Dispose 方法中将某些字段设置为 null 就足够了,此时不需要单独的布尔标志 - 你可以说 Dispose 已经被调用因为您已经将这些字段设置为 null

IDisposable 上的许多指导都非常无用,因为它解决了您需要终结器的情况,但这实际上是一个非常不寻常的情况。这意味着很多人编写了一个比必要的复杂得多的IDisposable 实现。在实践中,大多数课程都归类为 Stephen Cleary 在the article that jpierson linked to 中称为“1 级”的类别。对于这些,你不需要所有的GC.KeepAliveGC.SuppressFinalizeDispose(bool) 的东西,这些东西会弄乱大多数示例。大多数时候,生活实际上要简单得多,正如 Cleary 对这些“1 级”类型的建议所表明的那样。

【讨论】:

  • 谢谢伊恩,真的很有用。我有几个问题 1. 我们可以使用基类标志吗(因为我们正在调用 base.Dispose 并且它会负责设置标志)。 2. 如果是 UI 对象,它是否足以/建议仅实现 IDisposable(无终结器)?
  • 我同意 Ian 关于不需要布尔字段的说法。我建议遵循一个很好的约定,但在某些情况下,如果您有一个字段,如果为 null 就意味着 Dispose 已经发生。
  • 如果一个对象引用了其他不需要清理的对象,它不应该是可终结的。相反,清理所需的内容应封装在其自己的可终结对象中,该对象不应引用清理不需要的任何内容。
【解决方案2】:

需要复制

如果您在子类中没有任何清理,只需调用base.Dispose(),如果有一些类级别的清理,请在调用base.Dispose() 后进行。您需要将这两个类的状态分开,因此每个类都应该有一个 IsDisposed 布尔值。这样您就可以在需要时添加清理代码。

当您确定一个类为IDisposable 时,您只需告诉GC 我正在处理它的清理程序,您应该在该类上使用SuppressFinilize,以便GC 将其从队列中删除。除非您致电 GC.SupressFinalize(this),否则 IDisposable 类不会发生任何特殊情况。所以如果你按照我提到的那样实现它,就不需要Finilizer,因为你只是告诉GC不要完成它。

【讨论】:

  • 所以您建议使用第一个链接(FxCop 文档)中提供的模式?
  • 感谢 Tomas,我也认为这是一个好方法,但 MSDN 文章说(对于派生类)-“这个派生类没有 Finalize 方法或没有参数的 Dispose 方法,因为它继承自基类。”这是什么意思?
  • 虚拟 -1。您不需要重复。一个对象要么正在处理,要么正在完成。基类不是一个不同的对象,它只是同一个对象的基本行为。它要么正在完成,要么正在处理。
  • @akjoshi:这意味着不需要对派生类进行特殊清理,因此不需要对IDisposable 进行特殊实现。如果我们为每个类实现一个空模式,那就更浪费时间了。因此,如果您在派生类没有特殊清理,请不要覆盖基类。基类 Dispose 将被自动调用。但是如果你覆盖,记得打电话给base.Dispose()
  • @Alostad:如果我们不能访问基类代码怎么办。您需要在类级别而不是对象级别分离 disposing/finalizing 的行为。
【解决方案3】:

实现 IDisposable 的正确方法取决于您的类是否拥有任何非托管资源。实现 IDisposable 的确切方法仍然不是所有开发人员都同意的,有些像Stephen Cleary 这样的人对一次性范式有强烈的意见。

见:Implementing Finalize and Dispose to Clean Up Unmanaged Resources

IDisposable interface 的文档也简要说明了这一点,this article 指出了一些相同的内容,但也在 MSDN 上。

就基类中是否需要重复的布尔字段“isDisposed”而言。看起来这主要只是一个有用的约定,当子类本身可能添加需要处理的额外非托管资源时可以使用它。由于 Dispose 被声明为虚拟的,因此在子类实例上调用 Dispose 总是会导致首先调用该类的 Dispose 方法,然后调用 base.Dispose 作为最后一步,以便有机会清除继承层次结构中的每个级别。因此,我可能会将其总结为,如果您的子类在基类所拥有的资源之上有额外的非托管资源,那么您可能最好拥有自己的布尔 isDisposed 字段来跟踪它的 Dispose 方法内部的事务处理,但作为 Ian在他的回答中提到,还有其他方法可以表示已经处置的状态。

【讨论】:

  • 感谢 jpierson 的链接,看起来很有趣。
  • 基类 IsDisposed 标志对派生类很有用,如果所有处置操作都由同一个非重写类包装,该类对其执行 Interlocked.Exchange 以确保没有多余的处置操作。然而,正如 Microsoft 建议的实现所处理的那样,子类不能真正使用基本级别的 IsDisposed 标志。
【解决方案4】:

1) 无需重复

2) 实现终结器将有助于处置未明确处置的项目。但不保证。这是一个很好的做法。

【讨论】:

    【解决方案5】:

    仅当对象包含有关需要清理的内容的信息时才实现终结器,并且此信息以某种形式而不是对需要清理的其他对象的对象引用(例如,存储为 Int32 的文件句柄)。如果一个类实现了终结器,它不应该持有对任何其他不需要清理的对象的强对象引用。如果它包含其他引用,则负责清理的部分应使用终结器拆分为自己的对象,并且主对象应包含对此的引用。主对象不应该有终结器。

    只有在基类的目的是支持终结器时,派生类才应该有终结器。如果类的目的不以终结器为中心,那么允许派生类添加终结器就没有多大意义,因为派生类几乎肯定不应该(即使它们需要添加非托管资源,它们也应该将资源放入他们自己的类,只是持有对它的引用)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-30
      • 2016-03-22
      • 2010-09-11
      • 2017-10-27
      • 1970-01-01
      • 1970-01-01
      • 2012-02-15
      相关资源
      最近更新 更多