C#析构函数与IDisposable

时间:2018-05-20 09:09:48

标签: c# destructor finalizer

尝试理解为什么destructor在实例处置后被调用到USING范围之外。我知道destructor实施时IDisposable不需要using System; namespace Destructor { class Program { static void Main(string[] args) { using (var a = new A()) { Console.WriteLine("... inside USING ..."); } Console.WriteLine("...END..."); } } //............................................................ class A : IDisposable { public A() { Console.WriteLine("...Constructor called..."); } ~A() { Console.WriteLine("...Destructor called..."); } public void Dispose() { Console.WriteLine("...Dispose called..."); } } } 。这是我的例子:

// copy control within a template
NTree(const NTree& aOtherNTree)
{
    fKey = new T;
    fNodes[N] = new NTree<T, N>;

    fKey = aOtherNTree.fKey;
    for (int i = 0; i < N; i++)
    {
        fNodes[i] = aOtherNTree.fNodes[i];
    }
  

输出:

     

......构造函数叫...

     

......在USING中......

     

......处理叫......

     

... END ...

     

......析构函数叫......

2 个答案:

答案 0 :(得分:3)

直接回答您的问题 -

垃圾收集器调用析构函数 垃圾收集器线程将对象引用放在终结器队列上,终结器线程调用终结器(A.K.A.Destructor)。这就是您应该首先实现IDisposable接口的原因。

using语句实际上是try...finally的语法糖 - 当你写

using(var x = new MyDisposableClass())
{
    //  code here
}

与写作相同:

var x = new MyDisposableClass()
try
{
    //  code here
}
finally
{
    (IDisposable(x)).Dispose();
}

因此,c#会在Dispose块结束时调用实例的using方法。

但是,这并不意味着此实例将在第二次收集垃圾。事实上,它可以在系统中幸福地生活,直到垃圾收集器最终解决它。垃圾收集器在它自己的线程上运行,CLR决定何时激活它。您可以通过拨打GC.Collect()来激活它,但在大多数情况下这是不明智的 请阅读When is it acceptable to call GC.Collect?了解详情。

添加更多背景 - 当您使用非托管资源时,您可以通过在Dispose(bool)重载中编写代码或通过在Destructor中编写代码来释放它们。
Dispose(bool)中释放它们意味着您可以控制它们在调用代码中的释放时间(并且您通常希望尽快这样做)。

在析构函数中释放它们意味着它们仅在终结器线程执行析构函数时被释放(如果和),这意味着您无法控制何时在代码中释放它们。

此外,正确编写析构函数很难。如此努力以至于最好避免一开始就这样做 事实上,Eric Lippert撰写了一些关于此的博文,题为"When everything you know is wrong",这很难。

IDisposable的Reed Copsey的五篇博文系列中,还有一些更有用的信息 - 从IDisposable Part 1 – Releasing Unmanaged Resources开始。

奖金阅读 - Implementing a Dispose method

  

Dispose(布尔值)重载   在第二个重载中,disposing参数是一个布尔值,指示方法调用是来自Dispose方法(其值为true)还是来自终结器(其值为false)。

     

该方法的主体由两段代码组成:

     
      
  • 释放非托管资源的块。无论配置参数的值如何,都会执行此块。

  •   
  • 释放托管资源的条件块。如果disposing的值为true,则执行此块。它释放的托管资源可以包括:

  •   
     

实施IDisposable的托管对象。条件块可用于调用其Dispose实现。如果您使用安全句柄来包装非托管资源,则应在此处调用SafeHandle.Dispose(Boolean)实现。

     

占用大量内存或消耗稀缺资源的托管对象。在Dispose方法中显式释放这些对象比它们被垃圾收集器非确定地回收它们更快地释放它们。

最后一件事 - 你写道“我知道在实现IDisposable时不需要析构函数” - 这几乎是正确的。你几乎永远不应该为你的类型覆盖析构函数,正如理柏先生的博客所表明的那样。

答案 1 :(得分:1)

您无法知道何时调用析构函数。当垃圾收集器检测到对象符合收集条件时,将调用析构函数。这在不再需要资源之后的某个未确定的时间段发生。

您可以在此处详细阅读: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/dispose-pattern