正确实现Finalize和Dispose的方法(当父类实现IDisposable时)

时间:2010-12-14 12:16:28

标签: c# .net wpf idisposable finalizer

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

  1. 是否使用重复的isDisposed变量(因为它已经存在于基类中)或不使用?
  2. 是否在子类中实现终结器?
  3. 这两件事都是在这里给出的例子中完成的 -

    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

5 个答案:

答案 0 :(得分:6)

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

  

仅对对象实施Finalize   需要最终确定

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

在实践中,绝大多数时候你不想要终结者。 (.NET开发人员必须经历的学习曲线之一是发现在大多数地方他们认为他们需要终结器,但他们没有。)您已将此标记为(除其他事项外)WPF问题,以及我想说将终结器放在UI对象上几乎总是错误的。 (因此,即使您处于需要终结器的异常情况之一,该工作也不属于与WPF有关的代码附近。)

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

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

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

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

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

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

至于是否复制旗帜......其他答案在这里提供了相互矛盾的建议。要点是1)你需要调用基础Dispose和2)你的Dispose需要是幂等的。 (即,如果它被调用一次,两次,5次,100次也无关紧要 - 如果它不止一次被调用它就不应该抱怨。)你可以自由地实现它,不管你喜欢什么 - 布尔标志是一种方法,但我经常发现在我的null方法中将某些字段设置为Dispose就足够了,此时不需要单独的布尔标志 - 你可以告诉{{1}已调用1}},因为您已将这些字段设置为Dispose

null上的很多指导都非常无用,因为它解决了你需要终结器的情况,但这实际上是一个非常不寻常的情况。这意味着很多人编写的IDisposable实现远比必要复杂得多。在实践中,大多数课程都会调用Stephen Cleary在the article that jpierson linked to中称为“1级”的类别。对于这些,您不需要所有混淆大多数示例的IDisposableGC.KeepAliveGC.SuppressFinalize内容。生活在大多数时候实际上要简单得多,正如Cleary对这些“一级”类型的建议所示。

答案 1 :(得分:2)

需要重复

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

当您将某个班级确定为IDisposable时,您只需告诉我GC我正在处理它的清理程序,您应该SuppressFinilize在此课程上GC将它从队列中删除。除非你致电GC.SupressFinalize(this),否则IDisposable课程没有任何特殊情况。因此,如果您按照我的提法实施它,则不需要Finilizer,因为您刚刚告诉GC不要最终确定它。

答案 2 :(得分:1)

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

请参阅:Implementing Finalize and Dispose to Clean Up Unmanaged Resources

IDisposable interface的文档也解释了这一点,this article指出了一些相同的内容,但也指出了MSDN。

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

答案 3 :(得分:0)

1)无需复制

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

答案 4 :(得分:0)

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

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