C#中静态构造函数/初始化器的顺序

本文关键字:顺序 初始化 静态 构造函数 | 更新日期: 2023-09-27 17:47:49

在开发C#应用程序时,我刚刚注意到在一些地方静态初始化程序相互依赖,如下所示:

static private List<int> a = new List<int>() { 0 };
static private List<int> b = new List<int>() { a[0] };

没有做任何特别的工作。这只是运气吗?C#有解决这个问题的规则吗?

编辑:(re:Panos)在一个文件中,词法顺序似乎是王?跨文件怎么办?

在寻找过程中,我尝试了这样一种循环依赖:

static private List<int> a = new List<int>() { b[0] };
static private List<int> b = new List<int>() { a[0] };

程序运行不一样(测试服全面失败,我没有看得更远)。

C#中静态构造函数/初始化器的顺序

关于这里的规则,请参见C#规范的10.4节:

初始化一个类时,该类中的所有静态字段首先初始化为默认值,然后按文本顺序执行静态字段初始化程序。同样,当创建一个类的实例时,该实例中的所有实例字段首先初始化为其默认值,然后按文本顺序执行实例字段初始化程序。具有变量初始值设定项的静态字段可以在其默认值状态下观察到。然而,作为一种风格问题,我们强烈反对这样做。

换句话说,在您的示例中,"b"被初始化为其默认状态(null),因此在"a"的初始化器中对它的引用是合法的,但会导致NullReferenceException。

这些规则与Java的规则不同(关于Java关于前向引用的规则,请参见JLS的第8.3.2.3节,后者的限制性更强)。

这似乎取决于行的顺序。此代码有效:

static private List<int> a = new List<int>() { 1 };
static private List<int> b = new List<int>() { a[0] };

而此代码不起作用(它抛出一个NullReferenceException

static private List<int> a = new List<int>() { b[0] };
static private List<int> b = new List<int>() { 1 };

因此,显然不存在周期依赖性的规则。然而,奇怪的是编译器没有抱怨。。。


编辑-"跨文件"发生了什么?如果我们声明这两个类:

public class A {
    public static List<int> a = new List<int>() { B.b[0] };
}
public class B {
    public static List<int> b = new List<int>() { A.a[0] };
}

并尝试使用以下代码访问它们:

try { Console.WriteLine(B.b); } catch (Exception e) { Console.WriteLine(e.InnerException.Message.); }
try { Console.WriteLine(A.a); } catch (Exception e) { Console.WriteLine(e.InnerException.Message); }
try { Console.WriteLine(B.b); } catch (Exception e) { Console.WriteLine(e.InnerException.Message); }

我们得到的输出是:

The type initializer for 'A' threw an exception.
Object reference not set to an instance of an object.
The type initializer for 'A' threw an exception.

因此,B的初始化在静态构造函数A中引起异常,并将字段a保留为默认值(null)。由于anull,因此b也不能被正确地初始化。

如果我们没有周期性的依赖关系,一切都会很好。


编辑:Jon Skeet提供了一篇非常有趣的文章:静态构造函数和类型初始化器之间的区别,以防你没有阅读评论。

就我个人而言,我会去掉静态初始化器,因为它不清楚,并添加一个静态构造函数来初始化这些变量。

static private List<int> a;
static private List<int> b;
static SomeClass()
{
    a = new List<int>() { 0 };
    b = new List<int>() { a[0] };
}

然后你就不必猜测发生了什么,而且你的意图很清楚。

是的,你很幸运。C#似乎按照它在类中出现的顺序执行代码。
static private List<int> a = new List<int>() { 0 };
static private List<int> b = new List<int>() { a[0] };

会起作用,但是。。。

static private List<int> b = new List<int>() { a[0] };
static private List<int> a = new List<int>() { 0 };

将失败。

我建议将所有依赖项放在一个地方,静态构造函数就是这样做的地方。

static MyClass()
{
  a = new List<int>() { 0 };
  b = new List<int>() { a[0] };
}