c#我的析构函数不是';有人打电话吗?

c#我的析构函数不是';有人打电话吗?,c#,destructor,finalize,C#,Destructor,Finalize,我有一个简单的代码,试图调用析构函数,但我不能调用它:( 我知道GarbageCollector在必要时运行,所以我使用了GC.WaitForPendingFinalizers();但它也不起作用 这是我的密码: class Program { static void Main(string[] args) { Calculator calculator = new Calculator(); Console

我有一个简单的代码,试图调用析构函数,但我不能调用它:(

我知道GarbageCollector在必要时运行,所以我使用了GC.WaitForPendingFinalizers();但它也不起作用

这是我的密码:

class Program
    {
        static void Main(string[] args)
        {
            Calculator calculator = new Calculator();
            Console.WriteLine("{0} / {1} = {2}", 120, 15, calculator.Divide(120, 15)

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine("Program finishing");                           
        }

  }

class Calculator
    {

        // Constructor
        public Calculator()
        {
            Console.WriteLine("Calculator being created");
        }

        // Public Divide method
        public int Divide(int first, int second)
        {
            return first / second;
        }

        // Destructor
        ~Calculator()
        {
            Console.WriteLine("Destructor is called");

        }

    }
这是我的输出:

正在创建计算器

120/15=8

程序整理


我做错了什么?为什么我看不到“析构函数被调用”?

我不建议在析构函数.net上真正使用析构函数

无论如何,在您的情况下,GC在调用GS时不要认为您的对象是垃圾,因为堆栈中有活动链接计算器,它指向堆中的对象 因此,您可以尝试修改此代码

main(){
  DoCalculations();
  //at this point object calculator is garbage (because it was allocated in stack)
  GC.Collect();
}
DoCalculations(){
  Calculator calculator = new Calculator(); // object allocated
  calcualtor.doSomething();  //link alive
}

如果运行附加了调试器的程序,则会更改对象生存期的行为

在没有调试器的情况下,对象的最后一次使用在代码中传递后,就可以立即收集该对象。在连接了调试器的情况下,所有对象的生存期都会延长到对象在作用域内的整个时间,这样您就可以在调试器的
Watch
窗口中查看该对象,而不必使用调试器从你下面收集的东西

在调用
GC.Collect()之前,必须在未连接调试器的情况下以发布模式运行程序,或者将
calculator
设置为
null
使对象有资格进行垃圾收集并运行其终结器。

局部变量的生存期是声明它的局部变量范围内控件激活的生存期。因此,在main结束之前,您的局部变量是活动的。这足以解释为什么它没有被收集,但这里有一些微妙之处,我们应该更深入地探讨

生命周期可以通过各种机制来延长,包括通过lambda捕获外部变量、迭代器块、异步方法等

如果抖动可以证明这样做对单线程控制流没有影响,则允许缩短生存期。(可以使用
KeepAlive
确保在必须避免的情况下不会发生这种缩短。)

在您的情况下,允许运行时注意到不再从本地读取,将其标记为dead-early,从而孤立对对象的引用,然后收集并最终确定对象。不需要这样做,在您的情况下,显然也不需要这样做

另一个答案正确地指出:如果GC检测到调试器正在运行,它将故意抑制此优化,因为当您在调试器中检查包含对对象引用的变量时,收集对象的用户体验不好

让我们考虑一下我关于缩短寿命的陈述的含义,因为我认为你可能还没有完全掌握这些含义。

  • 允许运行时注意到,ctor从不访问此文件

  • 允许运行时注意到divide从不访问此文件

  • 运行时被允许注意到,因此本地数据从未被实际读取和使用

  • 因此,允许对象在其生命周期的任何时候都不在GC中扎根

  • 这意味着允许垃圾收集器在构造函数之前运行终结器

记住,GC和finalizer在它们自己的线程上运行;操作系统可以挂起主线程并在任何时候切换到GC和finalizer线程,包括在分配器运行之后但在控制传递给构造函数之前

在您编写的场景中,完全疯狂的事情是允许发生的;终结器没有运行是您的问题中最小的一个!当它可以运行时,这是可怕的

如果您不清楚这一事实,那么您就没有必要编写终结器。编写正确的终结器是C#中最难做的事情之一。如果您不是CLR垃圾收集器语义所有细节方面的专家,那么您不应该编写终结器

有关编写终结器的难度的更多想法,请参阅我关于此主题的系列文章,从这里开始:


事实并非如此:C#中的相关终结器不保证运行。在我的机器上,在您的代码(VS 2017,调试,任何CPU)中,终结器运行,并且“析构函数被调用”被打印出来。您可以理解GC的功能。在这里,规范正式地将这些“析构函数”称为“终结器”这种微妙的区别——终结器通常被认为是不确定的,析构函数是确定的——在C#社区中完全消失了,假设C#程序员理解这种区别是不明智的。我可以互换使用这些术语。GC可以考虑在局部变量(或任何与此相关的变量)能够证明其值永远不会被再次读取时,即使它仍在范围内,也会立即将其置死。(当然不需要这样做,但允许这样做。)@Roland Pashinev它工作得很好,谢谢。即使当GC.Collect运行时它不是垃圾,在我的代码末尾它不是垃圾吗?虽然这个答案使混合对象和包含引用它们的变量成为了一种常见的逻辑,但它基本上是正确的:GC知道它何时被调试,并且故意不那么激进。Wh我想这是一次对所有答案的集体否决,我的和另外两个答案(一个现在被删除)所有人都同时投了反对票。@ScottChamberlain其他两人都错了。更好的问题是,为什么现在有人对这些客观上不正确的答案投了反对票。@Servy我同意,但我想我在没有投票的情况下与他们混为一谈了