c# - 在 .NET 中,即使对象的构造函数从未运行过,终结器也可以运行吗?

标签 c# .net c++-cli finalizer

我知道在 .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 语句的分配部分以某种方式被重新排序并在前一个语句之前运行,但这种重新排序并未反射(reflect)在生成的 MSIL 代码中(打败了我最初的假设,即这只是另一个 C++/CLI代码生成错误)。此外,比较“错误”状态和任何“固定”状态之间生成的 MSIL 代码显示没有意外的结构变化。

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

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

问题

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

最佳答案

如评论中所述,答案是"is"——如果构造函数未运行或未完成,则终结器可以运行。但是,如果未发生分配(这与构造函数调用无关),终结器将无法运行。

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

关于c# - 在 .NET 中,即使对象的构造函数从未运行过,终结器也可以运行吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34447080/

相关文章:

c# - 对象.GetHashCode

c# - 在 C# 中读取 .db 文件

.net - 作为服务运行时,带有 git build 的 CruiseControl.Net 失败

winforms - 如何在 C++ winforms 中将水印文本添加到我的文本框(例如 "type here...")? (VS2012)

C# - 如何使用 Tesseract 3.0 Wrapper 获取每个字符的边界框?

c# - 如何将 CSV 文件读入 .NET 数据表

c# - 使用 Decimal.TryParse 转换屏蔽货币字符串

.net - .NET中的DateTime到Unix TimeStamp

使用 shared_ptr 作为参数时的 C++/CLI "could not import member"警告

c# - 该语言不支持“MethodName”