如何安全地解决 BeforeFieldInit 和静态构造函数循环?

How to safely work around BeforeFieldInit and static constructor cycles?

我担心以下两种行为之间的相互作用:

http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf#page=179

2.1. If the type is not yet initialized, try to take an initialization lock.

2.2.1. If not successful, see whether this thread or any thread waiting for this thread to complete already holds the lock.

2.2.2. If so, return since blocking would create a deadlock. This thread will now see an incompletely initialized state for the type, but no deadlock will arise.

http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf#page=69

If marked BeforeFieldInit then the type's initializer method is executed at, or sometime before, first access to any static field defined for that type.

此代码示例演示了可能的交互:

static class Foo {
  public static int A = 1;
}
static class Bar {
  public static int B = Foo.A;
}
static class Program {
  static void Main() {
    Console.WriteLine(Bar.B);
  }
}

在任何健全的环境中测试它时,它会输出 1。但是,规范似乎允许它通过执行以下操作输出 0:

  1. Main 开始执行。
  2. Foo 的类型初始值设定项开始执行(由于 BeforeFieldInit 规则,此时允许发生这种情况)。
  3. Bar 的类型初始化程序开始执行(由于 BeforeFieldInit 规则,此时允许发生这种情况)。
  4. Bar.B 的初始化程序开始执行。
  5. Foo.A 被请求。
  6. Foo 的类型初始值设定项已经 运行,等待它会导致死锁。死锁规则允许我们在未完全初始化的状态下查看 Foo,其中 A 尚未设置为 1,仍具有默认值 0。
  7. Bar.B 设置为 0。
  8. Bar 的类型初始化程序已完成。
  9. Foo.A 设置为 1。
  10. Foo 的类型初始值设定项已完成。
  11. 主要输出Bar.B,也就是0.

这真的允许吗?我应该如何编写类型初始值设定项才不会被它咬到?

Is this really allowed?

看起来确实是规范允许的。

When testing this in any sane environment, it will output 1.

没错。这有助于理解优化背后的原因。 "relaxed semantics" 的目的是将 "has the static constructor run?" 的检查从访问类型 的 执行时间移动到方法的 jit 时间它访问类型。也就是说,如果我们有:

void M()
{
    blah
    if blah blah
    ... Foo.A ...
    if blah blah blah
    ... Foo.A ...
    blah blah blah
}

假设现在是 M 的 jit 时间并且 Foo 的 cctor 尚未执行。为了严格遵守,抖动必须在 每次访问 Foo.A 时生成代码,以检查 Foo cctor 是否已执行,如果尚未执行则执行.

但是如果我们在 jit time 执行 cctor 调用 jitter 知道 FooM 等可以在 M 被 jitted 时调用 cctor,然后跳过在 M 中生成每个检查。

jitter 足够聪明,可以在 jit time 执行 cctor 时做正确的事情;它不会按照您描述的 "wrong" 顺序执行 cctor,因为编写抖动的人是理智的人,他们只是想让您的代码更快。

How am I supposed to write type initializers so I don't get bitten by it?

您应该假定符合规范的实现的作者是理智的。

如果出于某种原因您不能假设:您可以将您关心的所有静态字段初始值设定项放入静态构造函数中。 C# 编译器不允许对具有静态构造函数的类型使用 BeforeFieldInit 语义。