我理解在.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中对象的分配是否与该对象的构造函数调用分开并重新排序是有效的(因为缺少一个更好的术语)?