为什么.NET Object有方法Finalize()?

时间:2017-10-11 14:50:04

标签: c# .net garbage-collection finalizer

我知道垃圾收集器使用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可重用,而无需在每个类中实现它。实际上我很惊讶这样的基类不存在。

2 个答案:

答案 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的方法表中的一个槽,因此派生类可以覆盖它。它是否能以不同的方式完成是非常主观的。当然不容易,也不是没有强迫每一种语言实现特殊情况。