我理解在.NET中,即使对象是部分构造的(例如,如果从构造函数中抛出异常),也会运行终结器,但是什么时候构造函数从未运行过呢?
背景
我有一些C ++ / CLI代码可以有效地执行以下操作(我不相信这是特定于C ++ / CLI的,但这是我已经准备好的情况):
try {
ClassA ^objA = FunctionThatReturnsAClassA();
ClassB ^objB = gcnew ClassB(objA); // ClassB is written in C# in a referenced project
...
}
catch (...) {...}
我有100%可重复的情况,如果从FunctionThatReturnsAClassA()中抛出异常,然后触发GC(似乎通过再次运行此代码可靠地触发,但等待一段时间也有效),ClassB的终结者被召唤。
现在,通过跟踪输出我可以确认ClassB的构造函数没有运行(这当然是你所期望的)。所以不知何故,objB显然被分配并添加到终结器列表中,甚至在调用其构造函数的前提条件满足之前(即从FunctionThatReturnsAClassA()收集结果)。
这只发生在调试器外部运行的优化版本中。我可以进行各种小的更改,导致终结器没有运行 - 例如在两个语句之间插入另一个方法调用,或者(据说,我认为)将“gcnew ClassB”移动到一个单独的函数中,该函数返回对象
在我看来,不知何故gcnew语句的分配部分正在重新排序并在前一个语句之前运行,但是这个重新排序并没有反映在生成的MSIL代码中(打败了我最初的假设,这只是另一个C ++ / CLI代码gen bug)。此外,在“错误”状态和任何“固定”状态之间比较生成的MSIL代码显示没有意外的结构变化。
我已经查看了调试器中生成的x86代码,到目前为止它看起来并不奇怪,但我没有深入分析它,无论如何我无法在调试器中重现这种行为所以我我不是100%确定从调试器获得的代码与显示奇怪行为的代码相同。
所以它可能是一个MSIL-> x86代码的基本怪癖,或者它可能是一个处理器指令重新排序(前者似乎更有可能但我没有通过更加努力地在行为发生时更加努力地获取内存中的确切代码来确认 - 这是我的下一步。)
问题
因此,在.NET中分配一个对象并将其与该对象的构造函数调用分开重新排序,这是否有效(缺少一个更好的术语)?
答案 0 :(得分:1)
如评论中所述,答案是“是” - 如果构造函数未运行或未完成,则终结器可以运行。但是,如果未发生分配,则终结器无法运行(这与构造函数调用无关)。
现在确认这是一个JIT优化错误:https://github.com/dotnet/coreclr/issues/2478