不要仅仅因为一个类包含了另一个可处理的类,就在该类中实现终结器。考虑这样一个类,其中包括Dispose和终结器的三个清理场景:
1. 调用Dispose()
2. 应用程序关闭时调用终结器
3. 对象将被回收,但终结器未被禁止(通常是从调用Dispose()触发的,但请注意,在任何状态下都应禁用终结器,如果它不需要清理,则重新注册,如果它处于需要清理的状态-例如,如果您有一个Open()/Close()方法对)。
现在,如果您直接管理非托管资源(例如,通过IntPtr的句柄),那么这三种情况与您需要进行清理的三种情况匹配。
好的,现在让我们考虑一个正确实现了终结器的可处理包装器类。
~MyClass()
{
}
最终器实际上是没有任何作用的,因为它没有未管理的清理处理。唯一的影响是,如果这个空的最终器没有被禁用,那么在垃圾回收时它将被放入最终器队列中,使其以及只能通过它的字段访问的任何东西保持活动状态,并将它们提升到下一代 - 最终线程最终会调用此nop方法,将其标记为已最终化,并且它再次符合垃圾回收条件。但由于它被提升了,所以如果之前它属于第0代,现在就属于第1代,而如果之前它属于第1代,则现在属于第2代。
需要最终化的实际对象也会被提升,它不仅需要等待进行回收,还需要等待进行最终化。无论如何,它最终都将进入第2代。
好了,这已经够糟糕了。让我们假设我们真的在最终器中放置了一些代码来处理持有最终化类的字段。
等等,我们该怎么做?我们不能直接调用最终器,所以我们要释放它。哦等等,我们确定对于这个类,Dispose()方法的行为和最终器的行为足够接近而且是安全的吗?我们怎么知道它是否通过弱引用持有某些资源,它将尝试在Dispose方法中而不是在最终器中处理这些资源?该类的作者非常清楚在最终器内部处理弱引用会带来的风险,但是他们认为自己写了一个Dispose()方法,并非其他人的最终器方法的一部分。
然后,如果应用程序关闭时调用外部最终器,该怎么办?您怎么知道内部最终器是否已经被调用过了?当该类的作者编写他们的Dispose()方法时,他们是否确定要考虑“好的,现在让我们确保我处理了这个对象在最终器运行之后被调用的情况,唯一剩下的事情就是释放它的内存”?实际上并没有。它可能是以一种防止重复调用Dispose()的方式进行保护,以便在这种情况下也能得到保护,但是您不能真正指望它(特别是因为它们不会在最终器中提供帮助,他们知道这是最后一次被调用的方法,任何其他形式的清理,例如将不再使用的字段置空以标记它们,都是毫无意义的)。它最终可能会执行一些操作,例如降低某个引用计数资源的引用计数,或者违反它需要处理的未管理代码的合同。
因此,在这样一个类中使用最终器的最好情况是破坏了垃圾回收的效率,最糟糕的情况是您会遇到一个干扰完全良好的清理代码的bug。
请注意微软推广的模式及其背后的逻辑(现在仍然有一些类中使用),其中包含了一个受保护的
Dispose(bool disposing)
方法。现在,我要说这种模式很多缺点,但是当你看它时,它的设计正是为了应对以下事实:用
Dispose()
清理和用终结器(finaliser)清理的资源并不相同,这种模式意味着对象的直接持有的非托管资源将在两种情况下都被清理(在你提问的场景中,不存在此类资源),而托管资源,例如内部持有的
IDisposable
对象,则仅从
Dispose()
中清理,而不是从终结器中清理。
如果您需要清理任何东西,无论是非托管资源、
IDisposable
对象还是其他任何东西,都请实现
IDisposable.Dispose()
。
只有当您直接拥有需要清除的非托管资源时,请编写终结器,并确保在终结器中只清理该资源。
如果您想获得额外的分数,请避免同时处于两个类中 - 将所有非托管资源包装在可处理的和可终止的类中,这些类只与那些非托管类打交道,如果您需要将这种功能与其他资源结合使用,请使用仅可处理类来完成。这样清理将更加清晰、简单、不易出错,对GC效率的影响也会更小(没有一个对象的终结器会延迟另一个对象的终结器)。