C# 为什么对对象调用终结器

C# 为什么对对象调用终结器,c#,garbage-collection,finalizer,C#,Garbage Collection,Finalizer,下面是展示令人惊讶的终结行为的示例程序: class Something { public void DoSomething() { Console.WriteLine("Doing something"); } ~Something() { Console.WriteLine("Called finalizer"); } } namespace TestGC { class Program {

下面是展示令人惊讶的终结行为的示例程序:

class Something
{
    public void DoSomething()
    {
        Console.WriteLine("Doing something");
    }
    ~Something()
    {
        Console.WriteLine("Called finalizer");
    }
}

namespace TestGC
{
    class Program
    {
        static void Main(string[] args)
        {
           var s = new Something();
           s.DoSomething();
           GC.Collect();
           //GC.WaitForPendingFinalizers();
           s.DoSomething();
           Console.ReadKey();
        }
    }
}
如果我运行该程序,打印的内容是:

Doing something
Doing something
Called finalizer
这似乎与预期的一样。因为在调用
GC.Collect()
之后有一个对s的引用,所以s不是垃圾

现在删除行
//GC.WaitForPendingFinalizers()中的注释
再次生成并运行该程序

我预计产出不会有任何变化。这是因为我读到,如果发现对象是垃圾并且它有一个终结器,它将被放在终结器队列上。由于对象不是垃圾,所以不应该将其放在终结器队列中似乎是合乎逻辑的。因此,注释掉的行不应该做任何事情

但是,程序的输出为:

Doing something
Called finalizer
Doing something
有人能帮助我理解为什么会调用终结器吗

您的
DoSomething()
非常琐碎,很可能会被内联。内联之后,没有任何东西仍然具有对对象的引用,因此没有任何东西可以阻止它被垃圾收集


是专门为此场景设计的。如果要防止对象被垃圾收集,可以使用它。它什么也不做,但垃圾收集器不知道。调用
GC.KeepAlive
Main
的末尾,以防止它提前完成。

我无法在笔记本电脑上重现该问题,但您的
DoSomething
方法不使用对象中的任何字段。这意味着即使
DoSomething
正在运行,也可以最终确定对象

如果您将代码更改为:

class Something
{
    int x = 10;

    public void DoSomething()
    {
        x++;
        Console.WriteLine("Doing something");
        Console.WriteLine("x = {0}", x);
    }
    ~Something()
    {
        Console.WriteLine("Called finalizer");
    }
}
。。。然后我怀疑你总是会看到
在“调用终结器”之前打印两次
做某事,尽管最后的“x=12”可能会在“调用终结器”之后打印


基本上,定稿有点令人惊讶——我发现自己很少使用它,并鼓励您尽可能避免使用定稿器。

Jon的回答当然是正确的。我还想补充一点,C#规范要求编译器和运行时可以(但不要求)注意到局部变量中包含的引用永远不会被取消引用,并且在这一点上,如果局部变量是最后一个活引用,则允许垃圾收集器将该对象视为死对象。因此,即使活动局部变量中似乎存在引用,也可以收集对象并运行终结器。(同样,如果编译器和运行时愿意,它们也可以让局部变量活得更长。)

考虑到这一事实,你可能会陷入奇怪的境地。例如,当对象的构造函数在用户线程上运行时,终结器可能在终结器线程上执行。如果运行时可以确定“this”不再被取消引用,那么当构造函数对“this”字段进行变异时,对象就可以被视为死对象。如果构造器接着做额外的工作——比如说,改变全局状态——那么这些工作可以在终结器运行时完成


这就是为什么编写正确的终结器如此困难以至于您可能不应该这样做的另一个原因。在终结器中。你所指的一切都可能是死的,你在一个不同的线程上,对象可能没有完全构造,可能你的程序不变量都没有实际维护

事实上,这起了作用。谢谢现实生活中的例子是更复杂的C++/CLI程序,我需要终结器。@Tony:你有多确信你真的需要终结器?请参阅示例。更糟糕的是,当对象不存在强根引用时,对象有可能立即终止,但在终结器运行之前,强根引用需要重新建立,其他代码需要开始使用该对象。即使大多数物体不会自己复活,一个物体也很难防止被其他物体复活。