析构函数方法是否由垃圾收集器隐式使用,并且配置开发人员用于显式处理对象的方法?

时间:2012-01-04 15:10:44

标签: c# destructor dispose idisposable finalizer

我可以看到已经有很多关于dispose与析构函数方法的线程,但我只想确保在我继续之前正确理解它们。

垃圾收集器是否隐式使用析构函数方法,以便何时不再引用对象(即不再需要),并配置我们开发人员使用的方法来显式处理垃圾收集器可能无法处理的对象?

另外 - 我现在正在阅读所有这些内容,而这似乎是这些方法中的一个或另一个的情况。例如,给出以下代码:

class DansClass : IDisposable
{
    public void Dispose()
    {
        GC.SuppressFinalize(this);
        Console.WriteLine("Disposing...");
    }

    -DansClass()
    {
        Console.WriteLine("Destructing...");
    }
}

输出将是:

超声破坏...

可以理解,因为我们已经抑制了finalize(析构函数)所以我们只看到Dispose输出。

但如果我注释掉SuppressFinalize()方法,则输出为:

处置...

为什么析构函数也不被调用?

2 个答案:

答案 0 :(得分:6)

你看到的行为是因为Finalizers(你所谓的析构函数也被称为.Net中的终结器)排队等待垃圾收集器在后台运行。

最终将被调用(尽管在某些情况下可能不会)。但是,You can force their execution要了解发生了什么:

// Assuming you remove the GC.SuppressFinalize call
myDisposable.Dispose();

GC.WaitForPendingFinalizers();
// Output:
// Disposing...
// Destructing...

只有拥有外部资源或封装包含外部资源的成员时,才需要实现该模式。只有拥有外部非托管资源时才能实现终结器。

正确实施IDisposable模式需要:

  • Dispose 清理非托管和托管资源
  • 终结器清理挂起的非托管资源(如果不存在,则不需要)
  • 两者都不能抛出异常,Dispose必须可以多次调用
  • Finalizer应调用Dispose实现
  • 特定于域的终止方法,如Close,如果两者都存在,则在功能上等同于Dispose

答案 1 :(得分:1)

Dispose方法,终结器和C#具有讽刺意义的析构函数存在是因为许多对象要求其他实体代表他们做事,直到另行通知(通常授予对文件,内存区域,通信流,硬件设备,GDI句柄等);如果发出此类请求的对象在没有其他实体知道不再需要其服务的情况下消失,则代表被放弃对象留出的任何内容将无法无法访问。

IDisposable.Dispose方法提供了一种很好的一致方式来告诉对象它将不再被要求做任何需要其他实体帮助的事情,并且代表它做任何事情的任何其他实体应该被告知要停止这样做。如果正确调用IDisposable.Dispose,则对象可以最小化外部实体代表他们行事的程度。

不幸的是,由于各种原因(大多数情况很容易避免;有些情况下没有),有时候放弃对象而没有调用IDisposable.Dispose。这将导致外部实体必须至少在一段时间内代表被抛弃的对象无用地继续行动。为了避免外部实体代表外部实体永远行动,系统可以通知对象他们已经被放弃,从而使他们有机会向外部实体通知这一事实。每当创建一个类覆盖Object.Finalize的对象时,它将被放置在一个特殊的对象列表中,如果它们被放弃,它们将被通知。此列表上的存在本身并不足以使对象被视为“活动”,但在垃圾收集器从内存中删除死对象之前,它将检查它们是否在destroy-before-destruction列表中。列表中的所有死对象将从请求通知的对象列表中移动,如果/何时它们被放弃到需要通知他们已经被放弃的对象列表。在第二个列表上的放置将导致死对象和它们持有引用的任何对象再次被视为“活着”,至少在通知已执行之前。但是,当它们被移动到第二个列表时,它们将从第一个列表中删除,这样如果以后发现它们再次死亡,它们将从内存中清除而不另行通知。

垃圾收集完成后,如果任何对象位于需要通知放弃的对象列表中,系统将调用每个此类对象的Object.Finalize方法。通常,一旦在这样的对象上调用了Object.Finalize,就不再有对它的rooted引用了,它将在下一次垃圾收集时消失。但是,物体可能会复活。

在vb.net中,据我所知,大多数.net语言都会通过简单地以通常的方式声明覆盖来覆盖Object.Finalize。无论出于何种原因,C#的创造者决定禁止这样做。相反,在C#中,必须使用一种语言结构,具有讽刺意味地称为“析构函数”,以覆盖Finalize,以便发现被遗弃的对象在没有通知的情况下被销毁,但是而是有机会清理。

Object.Finalize的实际操作中有许多棘手的皱纹,除非绝对必要,否则最好避免依赖它。具有讽刺意味的“破坏者”实际上并没有破坏物体,而是推迟了它们的破坏。在对象上调用GC.SuppressFinalize()会将它从请求通知的对象列表中删除;如果在一个对象上调用Dispose,并且它又调用GC.SuppressFinalize()作为其 last 操作,则对象覆盖{{1}没有任何特别的危害或者声明一个“析构函数”,但一般来说,最好只在相对简单的类中覆盖Finalize(或声明“析构函数”)。