我有一个抽象基类,它实现了 IDisposable 和除析构函数之外的完整bool disposed = false
、Dispose()
和模式。Dispose(bool)
基类实现了 IDisposable,因为它的许多派生类需要释放非托管资源。但是,我听说带有析构函数的类很昂贵,因此如果我包含析构函数,那么没有非托管资源的派生类会变得不必要地昂贵。我对这个问题感到困惑。我应该还是不应该包含析构函数,为什么?谢谢。
4 回答
如果你正在实现一种全新的非托管资源,你只需要包含一个析构函数/终结器。因此,如果您只是包装或继承现有的数据库连接类型、套接字类型、gdi 资源等,那么您不需要析构函数。原始类型的析构函数将负责最终为您释放该资源。但是,如果您要从头开始为一种全新的数据库实现类似于 ADO.Net 提供程序对象的东西,那么您可能希望为您的连接类型实现一个析构函数,以便它可以在最终收集到它时释放它的连接。
理想情况下,Dispose 模式也依赖于终结器来完成。原因是要确保将清理非托管资源。这里的技巧是,在 Dispose 方法中,您还应该有以下调用:GC.SuppressFinalize(this),它指示垃圾收集器不要以特殊方式处理实例,这将使您免受终结的开销。因此,如果用户每次都正确处理对象(例如在 using 块中包装每次使用),则不会调用终结器,因此根本不会影响性能。
出于清理目的而应该重写的唯一类Finalize
是那些直接派生Object
并期望清理它们自己的资源的类,或者类似的类SafeHandle
,其目的是管理资源清理。否则,如果派生类具有需要清理的非托管资源,Finalize
而基类不会,则正确的方法通常是将每个单独的资源封装在其自己的可终结对象中。具有终结器的对象应避免持有对终结器不需要的任何对象的强引用,因为它们持有引用的所有对象,以及任何这些对象持有引用的所有对象等都将保持活动状态。额外的垃圾收集生成。如果一个对象George
它持有指向许多其他对象的链接,持有对不具有强反向链接的可终结对象的引用,并且George
被放弃,可终结对象将需要保留以进行额外的 GC 生成,但George
它和它所指向的其他对象持有直接和间接引用不会。相比之下,如果George
它自己实现Finalize
了,那么它和它持有直接或间接引用的每个对象都必须保留。
此外,如果最后一次使用大型可终结对象实际上是对其资源之一的使用,则终结有时会导致一些罕见但难以追踪的 Hindenbugs。使用可以清理的资源的代码Finalize
必须确保包含这些资源的对象在这些资源仍在使用时不符合最终确定的条件。这通常通过使用GC.KeepAlive()
. 如果派生类添加了Finalize
清理父类中存在但父类不希望终结器清理的任何资源的方法,可能会出现错误。将资源封装在自己的类中可以避免这个问题(在使用资源时父对象可能会被垃圾回收,但如果封装对象设计得当,则无关紧要——封装对象的Finalize
方法赢了'直到它的方法使用完资源后才运行)。
我听说带有析构函数的类很昂贵
具有终结器或实现的IDisposable
类并不比没有终结器的类更昂贵。然而,一个类的实现IDisposable
告诉调用者他们需要在不再需要时被跟踪和清理。这对调用者来说是额外的工作,但不这样做的代价是资源泄漏,至少在类被垃圾收集之前是这样。
简而言之,如果你的类不使用任何需要清理的资源,通常以同样实现 IDisposable 的字段的形式出现,你就不需要终结器。