为什么在静态构造函数中初始化静态字段是如此危险的行为?

摘要:C#具有一个默认开启的代码分析规则:[CA1810]Initialize reference type static fields inline,推荐我们以内联的方式初始化静态字段,而不是将初始化放在静态构造函数中。一、两种初始化的性能差异
C#具有一个默认开启的代码分析规则:[CA1810]Initialize reference type static fields inline,推荐我们以内联的方式初始化静态字段,而不是将初始化放在静态构造函数中。 一、两种初始化的性能差异 二、beforefieldinit标记 三、静态构造函数执行的时机 四、关于“All-Zero”结构体 五、RuntimeHelpers.RunClassConstructor方法 一、两种初始化的性能差异CA1810这一规则与性能有关,我们可以利用如下这段简单的代码来演示两种初始化的性能差异。Foo和Bar这两个类的静态字段都定义了一个名为_value的静态字段,它们均通过调用静态方法Initialize返回的值进行初始化。不同的是Foo以内联(inline)赋值的方法进行初始化,而Bar则将初始化操作定义在静态构造函数中。假设Initialize方法是一个相对耗时的操作,我们利用Program的_initialized字段判断该方法是否被调用。 static class Program { private static bool _initialized; static void Main() { Foo.Invoke(); Debug.Assert(_initialized == false); Bar.Invoke(); Debug.Assert(_initialized == true); } private static int Initialize() { _initialized = true; return 123; } public class Foo { private readonly static int _value = Initialize(); public static int Value => _value; public static void Invoke() { } } public class Bar { private readonly static int _value; public static int Value => _value; static Bar() => _value = Initialize(); public static void Invoke() { } } }从我们给出的调用断言可以确定,当我们调用Foo的静态方法Invoke时,它的静态字段_value并没有初始化;但是当我们调用Bar的Invoke方法时,Initialize方法会率先被调用来初始化静态字段。从这个例子来说,由于整个应用并没有使用到Foo和Bar的静态字段,所以针对它们的初始化是没有必要的。所以我们说以内联方式对静态字段进行初始化的Foo具有更好的性能。 二、beforefieldinit标记对于Foo和Bar这两个类型表现出来的不同行为,我们可以试着从IL代码层面寻找答案。如下所示的两段IL代码分别来源于Foo和Bar,我们可以看到虽然Foo类中没有显式定义静态构造函数,但是编译器会创建一个默认的静态构造函数,针对静态字段的初始化就放在这里。我们可以进一步看出,自动生成的这个静态构造函数和我们自己写的并没有本质的不同。两个类型之间的差异并没有体现在静态构造函数上,而是在于:没有显式定义静态构造函数的Foo类型上具有一个beforefieldinit标记。
阅读全文