Ton*_*ony 3 c# garbage-collection finalizer
这是一个展示令人惊讶的终结行为的示例程序:
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果我运行该程序,打印的是:
Doing something
Doing something
Called finalizer
Run Code Online (Sandbox Code Playgroud)
这似乎符合预期.因为在调用之后有一个对s的引用GC.Collect(),s不是垃圾.
现在从行//GC.WaitForPendingFinalizers();
构建中删除注释 并再次运行该程序.
我希望输出中没有任何改变.这是因为我读到如果发现对象是垃圾并且它有终结器,它将被放在终结器队列中.由于对象不是垃圾,因此它不应该放在终结器队列上似乎是合乎逻辑的.因此,注释掉的那条线应该什么都不做.
但是,该计划的输出是:
Doing something
Called finalizer
Doing something
Run Code Online (Sandbox Code Playgroud)
有人可以帮助我理解为什么终结器会被调用吗?
我无法在笔记本电脑上重现问题,但您的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");
}
}
Run Code Online (Sandbox Code Playgroud)
...然后我怀疑你将永远看DoingSomething之前"被叫终结"打印两次-尽管最终"X = 12"可能"叫终结"后打印.
基本上,最终确定可有点让人吃惊-我非常很少用它发现自己,并会鼓励你,以避免终结尽可能.
乔恩的回答当然是正确的.我想补充一点,C#规范调用允许编译器和运行时(但不是必须)注意到局部变量中包含的引用永远不再被取消引用,并且此时允许垃圾收集器处理如果本地是最后的生活参考,则该对象为死亡.因此,即使在生活局部变量中似乎存在引用,也可以收集对象并且终结器运行.(同样地,如果他们愿意,允许编译器和运行时使本地人的寿命更长.)
鉴于这一事实,你最终可能会陷入奇怪的境地.例如,当对象的构造函数在用户线程上运行时,终结器可以在终结器线程上执行.如果运行时可以确定"this"永远不再被解除引用,那么在构造函数完成"this"字段变异的那一刻,该对象可以被视为死.如果构造函数然后执行其他工作 - 比如改变全局状态 - 那么可以在终结器运行时完成该工作.
这也是为什么编写正确的终结器非常困难以至于你可能不应该这样做的另一个原因.在终结器中你知道的一切都是错的.你所指的一切都可能已经死了,你在一个不同的线程,对象可能没有完全构造,可能没有你的程序不变量实际维护.