在.NET中,即使对象';的构造函数从未运行

本文关键字:构造函数 运行 NET 对象 | 更新日期: 2023-09-27 18:27:52

我知道在.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的构造函数没有运行(这当然是您所期望的)。因此,在满足调用其构造函数的先决条件(即从FunctionThatReturnsAClassA()收集结果)之前,objB显然已经被分配并添加到了终结器列表中。

这种情况只发生在调试器之外运行的优化版本构建中。我可以做各种小的更改,导致终结器不运行——例如,在两个语句之间插入另一个方法调用,或者(我认为很明显)将"gcnew ClassB"移动到一个返回对象的单独函数中。

在我看来,不知何故,gcnew语句的分配部分被重新排序,并在前一条语句之前运行,但这种重新排序并没有反映在生成的MSIL代码中(这打破了我最初的假设,即这只是另一个C++/CLI代码生成错误)。此外,将生成的MSIL代码在"bug"状态和任何"固定"状态之间进行比较,不会显示出意外的结构变化。

我也在调试器中查看了生成的x86代码,到目前为止,它看起来并不奇怪,但我还没有对其进行深入分析,而且无论如何,我无法在调试器中重现这种行为,所以我不能100%确定从调试器中获得的代码与显示奇怪行为的代码相同。

因此,它可能是MSIL->x86代码生成的怪癖,也可能是处理器指令的重新排序(前者似乎更有可能,但我还没有通过在行为发生时更努力地在内存中获取确切的代码来确认——这是我的下一步)。

问题

那么,将.NET中对象的分配与该对象的构造函数调用分开并重新排序是否有效(因为没有更好的术语)?

在.NET中,即使对象';的构造函数从未运行

正如注释所述,答案是"是"——如果构造函数没有运行或没有完成,则终结器可以运行。但是,如果没有发生分配(这与构造函数调用无关),则终结器无法运行。

现在已确认这是JIT优化错误:https://github.com/dotnet/coreclr/issues/2478