我知道垃圾收集器使用Finalize方法让对象释放非托管资源。根据我所知,Object.Finalize永远不会被GC直接调用(如果它的类型通过实现终结器来覆盖 Finalize方法,则在构造期间将对象添加到f-reachable队列中)。
仅从自动生成的终结器代码调用Object.Finalize:
try
{
//My class finalize implementation
}
finally
{
base.Finalize(); // Here chain of base calls will eventually reach Object.Finalize/
}
因此,从Object派生的任意类不会调用Object.Finalize - 你需要Object.Finalize的终结器才有意义,对于大多数类而言,它没有意义并且未使用(不是说它的实现是事实上是空的。)
在没有覆盖Object.Finalize 的情况下检查类中Finalize方法的存在是否太复杂,并且在没有try {}的情况下生成根终结器最终{ base.Finalize()< / strong>}来电?与用于集合初始化的Add
方法类似的东西 - 您不必实现任何接口或覆盖该方法 - 只需实现public void Add(item)
方法。
它会使C#编译器有点复杂,但是通过删除一个冗余调用使得终结器运行得稍微快一些,最重要的是 - 如果没有带有空实现的受保护Finalize
方法,那么使得Object类更容易理解需要完成任何事情。
也可以实现从Object派生的FinalizableObject类,并使编译器派生所有具有终结器的类。它可以实现IDisposable并使disposing pattern, recommended by Microsoft可重用,而无需在每个类中实现它。实际上我很惊讶这样的基类不存在。
答案 0 :(得分:10)
修改强>
除非重写该方法,否则垃圾收集不会调用Object.Finalise
的子实现。为什么它可用于所有对象?因此,可以在需要时被覆盖,但除非它没有性能影响。查看文档here,它指出;
Object类没有为Finalize方法提供任何实现,并且垃圾收集器不会标记从Object派生的类型以进行最终化,除非它们覆盖Finalize方法。
关于最终确定的说明
直接引用Ben Watson的优秀书籍编写高性能.NET代码,因为他解释得比以往任何时候都要好得多;
除非需要,否则不要实施终结器。终结器是代码,由垃圾收集器触发以清理非托管资源。它们从一个线程一个接一个地调用,并且只有在垃圾收集器在收集后声明对象死亡之后才会调用它们。这意味着如果你的类实现了一个终结器,你就可以保证即使在应该杀死它的集合之后它也会保留在内存中。这会降低整体GC效率,并确保您的程序将专用CPU资源来清理对象。
如果您确实实现了终结器,则还必须实现
IDisposable
界面以启用显式清理,并调用GC.SuppressFinalize(this)
在Dispose
方法中从终结队列中删除对象。 只要您在下一个集合之前调用Dispose
,它就会正确清理对象,而无需运行终结器。以下示例正确演示了此模式;
class Foo : IDisposable
{
~Foo()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this.managedResource.Dispose();
}
// Cleanup unmanaged resource
UnsafeClose(this.handle);
// If the base class is IDisposable object, make sure you call:
// base.Dispose();
}
}
注意有些人认为终结器可以保证运行。这通常是正确的,但并非绝对如此。如果程序被强制终止 然后没有更多的代码运行,并且该过程立即死亡。在过程关闭时给出所有终结器的时间也有一个时间限制。如果您的终结器位于列表的末尾,则可以跳过它。此外, 因为终结器按顺序执行,如果另一个终结器中有一个无限循环错误,那么在它运行之后就没有终结器。虽然终结器不在GC线程上运行,但它们由GC触发,因此如果您没有集合,则终结器将不会运行。因此,您不应该依赖终结器来清理流程外部的状态。
Microsoft对终结器和Disposable模式here
进行了很好的描述答案 1 :(得分:8)
C#语言析构函数语法模糊了终结器真正的作用。也许最好用示例程序证明:
using System;
class Program {
static void Main(string[] args) {
var obj = new Example();
obj = null; // Avoid debugger extending its lifetime
GC.Collect();
GC.WaitForPendingFinalizers();
Console.ReadLine();
}
}
class Base { ~Base() { Console.WriteLine("Base finalizer called"); } }
class Derived : Base { ~Derived() { Console.WriteLine("Derived finalizer called"); } }
class Example : Derived { }
输出:
派生的终结者称为 基本终结器名为
这种行为有一些值得注意的事情。 Example类本身没有终结器,但它的基类终结器仍然被调用。在Base类终结器之前调用Derived类终结器不是偶然的。请注意,Derived类的终结器没有调用base.Finalize(),即使Object.Finalize()的MSDN文章要求它,但它仍然被调用。
您可以轻松识别此行为,这是virtual
方法的行为方式。其覆盖调用基本方法的方法,如虚拟方法覆盖一般。否则正好它在CLR中的内容,Finalize()是一个普通的虚拟方法,就像任何其他方法一样。 C#编译器为Derived类'析构函数生成的实际代码类似于:
protected override Derived.Finalize() {
try {
Console.WriteLine("Derived finalizer called");
}
finally {
base.Finalize();
}
}
不是有效的代码,但它可以从MSIL进行反向工程。 C#语法糖确保您永远不会忘记调用基本终结器,并且它不能被线程中止或AppDomain卸载中止。 C#编译器没有帮助并为Example类自动生成终结器; CLR执行必要的工作,找到最派生类的终结器,遍历基类的方法表,直到找到它为止。同样通过设置一个标志来帮助类加载器,以指示Example具有带有终结器的基类,因此需要由GC专门处理。 Base类终结器调用Object.Finalize(),即使它没有做任何事情。
所以关键点是Finalize()实际上是一个虚方法。因此,它需要Object的方法表中的一个槽,因此派生类可以覆盖它。它是否能以不同的方式完成是非常主观的。当然不容易,也不是没有强迫每一种语言实现特殊情况。