C# 有人能解释这种最终定稿行为吗
在“调查”最终定稿(阅读:尝试愚蠢的事情)时,我偶然发现了一些意想不到的行为(至少对我而言) 我希望finalize方法不会被调用,而它会被调用两次C# 有人能解释这种最终定稿行为吗,c#,.net-4.0,clr,destructor,finalizer,C#,.net 4.0,Clr,Destructor,Finalizer,在“调查”最终定稿(阅读:尝试愚蠢的事情)时,我偶然发现了一些意想不到的行为(至少对我而言) 我希望finalize方法不会被调用,而它会被调用两次 class Program { static void Main(string[] args) { // The MyClass type has a Finalize method defined for it // Creating a MyClass places a reference to
class Program
{
static void Main(string[] args)
{
// The MyClass type has a Finalize method defined for it
// Creating a MyClass places a reference to obj on the finalization table.
var myClass = new MyClass();
// Append another 2 references for myClass onto the finalization table.
System.GC.ReRegisterForFinalize(myClass);
System.GC.ReRegisterForFinalize(myClass);
// There are now 3 references to myClass on the finalization table.
System.GC.SuppressFinalize(myClass);
System.GC.SuppressFinalize(myClass);
System.GC.SuppressFinalize(myClass);
// Remove the reference to the object.
myClass = null;
// Force the GC to collect the object.
System.GC.Collect(2, System.GCCollectionMode.Forced);
// The first call to obj's Finalize method will be discarded but
// two calls to Finalize are still performed.
System.Console.ReadLine();
}
}
class MyClass
{
~MyClass()
{
System.Console.WriteLine("Finalise() called");
}
}
有人能解释一下这种行为是否是故意的吗?如果是,为什么
上述代码是在x86调试模式下编译的,并在CLR v4上运行
非常感谢有趣的数据点:
- linux上的mono 2.10.8.1不调用终结器
- linux上的mono 2.8不调用终结器:
- Win32上的mono 2.8.1不调用终结器
- Win32上的mono 2.6.7不调用终结器
- Win32上的.NET 3.5调用终结器两次
class Program
{
static void Main(string[] args)
{
// The MyClass type has a Finalize method defined for it
// Creating a MyClass places a reference to obj on the finalization table.
var myClass = new MyClass();
// Append another 2 references for myClass onto the finalization table.
System.GC.ReRegisterForFinalize(myClass);
System.GC.ReRegisterForFinalize(myClass);
// There are now 3 references to myClass on the finalization table.
System.GC.SuppressFinalize(myClass);
System.GC.SuppressFinalize(myClass);
System.GC.SuppressFinalize(myClass);
// Remove the reference to the object.
myClass = null;
// Force the GC to collect the object.
System.GC.Collect(2, System.GCCollectionMode.Forced);
// The first call to obj's Finalize method will be discarded but
// two calls to Finalize are still performed.
}
}
class MyClass
{
~MyClass()
{
System.Console.WriteLine("Finalise() called");
}
}
我不知道是什么导致了这种奇怪的行为。但是,由于您违反了该方法的使用记录,任何事情都可能发生。ReRegisterForFinalize的文档说明: 请求系统为以前调用过SuppressFinalize的指定对象调用终结器。 在调用ReRegisterForFinalize之前,您没有调用SuppressFinalize。文档没有说明在这种情况下会发生什么,事实上,很明显,发生了一些非常奇怪的事情 不幸的是,同一文档页接着显示了一个示例,其中对尚未调用SuppressFinalize的对象调用了ReRegisterForFinalize 这有点乱。我会和文档经理商量的
当然,这个故事的寓意是,如果你违反了文档中描述的规则,那么就停止违反这些规则。我怀疑这属于“未定义行为”的范畴。如果您查看和的文档,他们会说: obj参数必须是此方法的调用方 你的代码不是这样的。我猜。。。这真的只是一个猜测。正如埃里克所说,不要打破这样的规则:)这种猜测只是为了无聊的投机和兴趣 我怀疑涉及两种数据结构:
- 终结队列
- 对象的标题
SuppressFinalization
的调用阻止了这种行为
另外,终结器线程通过终结队列运行,并为找到的所有内容调用终结器。您对ReRegisterForFinalize
的调用绕过了引用在队列中结束的正常方式,并直接添加了引用SuppressFinalization
并没有从队列中删除引用-它只是阻止引用以正常方式添加到队列中
所有这些都可以解释你所看到的行为(我也复制了这些行为)。它还解释了为什么当我删除
SuppressFinalization
调用时,我最终会看到终结器被调用了三次——因为在这种情况下,“正常”路径也会将引用添加到终结队列中。如果将其中一个SuppressFinalization
调用移到两个ReRegisterForFinalization
之前,它只被调用一次。基本上,在ReRegisterForFinalize
调用之间必须至少有1个SuppressFinalize
交错调用。谢谢@Eric,我知道代码违反了规范,我肯定不会提倡在合理的程序中使用上述任何一个。嘿,受虐狂也是程序员。不要践踏少数群体,兄弟。很好的拾取,尽管有趣的是,如果在重新注册forfinalize
调用之间不交错至少1次SuppressFinalize
,在.Net上仍然会发生。很好的调用,Jon。查看MS不久前发布的CLI源代码,在内部ReRegisterForFinalize()
方法中有一条if
语句,该语句表示“如果我已被标记为已完成,请删除该标记。如果没有,请将我添加到队列中。”因此,如果您在不首先抑制的情况下调用了两次ReReg,该对象只是被添加到队列中两次。然后,suppress将位清除三次(实际上,这没有任何作用,因为位从未设置为开始!)在任何情况下,都是精彩的推测,一如既往。谢谢@Jon和dlev,这正是我所怀疑的。然而,这与我之前假设的(并且可以证明是不正确的)心智模型相反,即一种方法从终结表中添加,另一种方法从终结表中删除。这显然违反了文件规定的用法,但似乎是一种疏忽,即如果某个对象被重新注册以进行最终确定,而该对象本不应该被取消,则它永远无法被取消,我很好奇原因是什么——我假设是性能原因。sehe对mono行为的研究非常有趣,我想这就是规范中未定义行为的美妙之处!