为什么Dispose()是非虚拟的?

时间:2010-09-01 15:04:31

标签: c# .net dispose idisposable

我是C#的新手,如果这是一个明显的问题,请道歉。

MSDN Dispose example中,他们定义的Dispose方法是非虚拟的。这是为什么?这对我来说似乎很奇怪 - 我希望IDisposable的子类具有自己的非托管资源,它只会覆盖Dispose并在它们自己的方法的底部调用base.Dispose()。

谢谢!

8 个答案:

答案 0 :(得分:14)

典型的用法是Dispose()被重载,具有公共的非虚拟Dispose()方法和虚拟的受保护的Dispose(bool)。公共Dispose()方法调用Dispose(true),子类可以使用此受保护的虚方法释放自己的resorces,并为父类调用base.Dispose(true)。

如果拥有公共Dispose()方法的类也实现了终结器,则终结器调用Dispose(false),表示在垃圾回收期间调用了受保护的Dispose(bool)方法。

如果有终结器,那么公共Dispose()方法也负责调用GC.SuppressFinalize()以确保终结器不再处于活动状态,并且永远不会被调用。这允许垃圾收集器正常处理类。具有活动终结器的类通常仅在gen0,gen1和gen2清理后作为最后的手段收集。

答案 1 :(得分:8)

这当然不是一个明显的问题。这种模式特别选择,因为它在以下场景中运行良好:

  • 没有终结器的类。
  • 有终结器的类。
  • 可以继承的类。

虽然虚拟Dispose()方法可以在类不需要最终化的场景中工作,但是如果您确实需要最终化,它在场景中不能很好地工作,因为这些类型通常需要两种类型的清理 - 起来。即:托管清理和非托管清理。出于这个原因,在模式中引入了Dispose(bool)方法。它可以防止重复清理代码(其他答案中缺少这一点),因为Dispose()方法通常会清理托管和非托管资源,而终结器只能清理非托管资源。

答案 2 :(得分:5)

虽然接口中的方法不是通常意义上的“虚拟”,但它们仍然可以在继承它们的类中实现。这显然是C#语言内置的一种便利,允许创建接口方法而无需virtual关键字,并且无需override关键字即可实现方法。

因此,尽管IDisposable界面包含Dispose()方法,但它前面没有virtual关键字,您也不必使用override继承类中的关键字来实现它。

通常的Dispose模式是在您自己的类中实现Dispose,然后在基类中调用Dispose以便它可以释放它拥有的资源,依此类推。

  

类型的Dispose方法应该释放   它拥有的所有资源。它   还应该释放所有资源   通过调用它的基类型来拥有它   父类型的Dispose方法。该   父类型的Dispose方法应该   释放它拥有的所有资源   反过来调用其父类型的Dispose   方法,传播这种模式   通过基础类型的层次结构。

http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx

答案 3 :(得分:3)

通过接口进行的呼叫始终是虚拟的,无论“正常”呼叫是直接呼叫还是虚拟呼叫。如果实际执行处理工作的方法不是虚拟的,除非通过接口调用,那么只要该类想要自行处理它,就必须确保将其自引用转换为iDisposable并调用它。

在模板代码中,非虚拟Dispose函数在父节点和子节点中始终是相同的[简单地调用Dispose(True)],因此永远不需要覆盖它。所有工作都在虚拟Dispose(布尔)中完成。

坦率地说,我认为在没有理由期望后代类直接持有非托管资源的情况下,使用Dispose模式有点愚蠢。在.net的早期阶段,类通常需要直接保存非托管资源,但是在大多数情况下,我认为直接实现Dispose()只会造成零损失。如果未来的后代类需要使用非托管资源,它可以并且通常应该将这些资源包装在它们自己的Finalizable对象中。

另一方面,对于某些类型的方法,使用非虚拟基类方法可能是有利的,该方法的作用是链接到受保护的虚方法,并且将虚方法称为Dispose(bool)是真的不比VirtDispose()差,即使提供的参数相当无用。例如,在某些情况下,对象的所有操作都可能需要由基类对象拥有的锁保护。让非虚基类Dispose在调用虚方法之前获取锁定将使所有基类免于担心锁本身。

答案 4 :(得分:3)

Dispose方法不应该是虚拟的,因为它不是模式实现一次性的扩展点。这意味着层次结构中的基本一次性类将为dispose创建顶级策略(算法),并将详细信息委托给另一个方法(Dispose(bool))。 此顶级策略是稳定的,不应被子类重写。如果允许子类覆盖它,它们可能不会调用算法的所有必要部分,这可能会使对象保留不一致的状态。

这类似于template method pattern,其中高级方法实现算法框架并将细节委托给其他可覆盖的方法。

作为旁注,我更倾向于此特定模式的another高级策略(仍然使用非虚拟Dispose)。

答案 5 :(得分:2)

示例的Dispose()方法是非虚拟的原因是因为它们接管了该示例中的整个过程,并使用虚拟Dispose(bool disposing)方法保留子类来覆盖。您会注意到,在示例中,它存储了一个布尔字段,以确保Dispose逻辑不会被调用两次(可能一次来自IDisposable,一次来自析构函数)。覆盖提供的虚拟方法的子类不必担心这种细微差别。这就是为什么示例中的主Dispose方法是非虚拟的。

答案 6 :(得分:1)

我对配置模式here有一个非常详细的解释。从本质上讲,您提供了一个protected方法来覆盖,这对于非托管资源来说更加健壮。

答案 7 :(得分:0)

如果基类具有需要在Dispose()时间清理的资源,那么具有被继承类覆盖的虚拟Dispose方法会阻止释放这些资源,除非继承类具体打电话给 base的Dispose方法。实现它的更好方法是让每个派生类实现IDisposable