46

我做了一些搜索,我认为以下代码可以保证产生输出:

B.X = 7

B.X = 0

A.X = 1

A = 1, B = 0
static class B
{
    public static int X = 7;
    static B() {
        Console.WriteLine("B.X = " + X);
        X = A.X;
        Console.WriteLine("B.X = " + X);
    }
}

static class A
{
    public static int X = B.X + 1;
    static A() {
        Console.WriteLine("A.X = " + X);
    }
}

static class Program
{
    static void Main() {
        Console.WriteLine("A = {0}, B = {1}", A.X, B.X);
    }
}

我已经运行了很多次,并且总是在代码部分上方得到输出;我只是想验证它会改变吗?就算在文字上,班级A和班级B都重新安排了?

是否保证第一次使用静态对象会触发其静态成员的初始化,然后实例化其静态构造函数?对于这个程序,A.X在 main 中使用会触发 的初始化,然后A.X依次初始化B.X,然后B()在完成初始化之后A.X,将继续执行A()。最后,Main()将输出A.X和BX`。

4

4 回答 4

55

直接来自 ECMA-334:

17.4.5.1:如果类中存在静态构造函数(第 17.11 节),则在执行该静态构造函数之前立即执行静态字段初始化程序。否则,静态字段初始化程序在依赖于实现的时间在执行之前执行首次使用该类的静态字段。”

和:

17.11:静态构造函数的执行由在应用程序域内发生的以下事件中的第一个触发:

  • 类的一个实例被创建。
  • 类的任何静态成员都被引用。

如果一个类包含开始执行的 Main 方法(第 10.1 节),则该类的静态构造函数在调用 Main 方法之前执行。如果一个类包含任何带有初始值设定项的静态字段,则这些初始值设定项会在执行静态构造函数之前立即按文本顺序执行(第 17.4.5 节)。

所以顺序是:

  • A.X用过,static A()所谓的。
  • A.X需要初始化,但它使用B.X,static B()所谓的。
  • B.X需要初始化,初始化为7。B.X = 7
  • 的所有静态字段B都被初始化,因此static B()被调用。X打印(“7”),然后设置为A.X. A已经开始初始化,所以我们得到 的值A.X,它是默认值(“当一个类被初始化时,该类中的所有静态字段首先被初始化为它们的默认值”);B.X = 0, 并打印 ("0")。
  • 完成初始化B,并将 的值A.X设置为B.X+1A.X = 1.
  • 的所有静态字段A都被初始化,因此static A()被调用。A.X打印(“1”)。
  • 回到,打印和Main的值(“1”,“0”)。A.XB.X

它实际上在标准中对此进行了评论:

17.4.5: 可以在默认值状态下观察具有变量初始值设定项的静态字段。但是,作为风格问题,强烈建议不要这样做。

于 2010-09-09T23:20:45.483 回答
9

C# 规范中大约有四个不同的规则涉及做出这种保证,并且它是特定于 C# 的。.NET 运行时做出的唯一保证是类型初始化在使用类型之前开始。

  • 在类型初始化程序运行之前,静态字段被初始化为零。
  • 静态字段初始化程序在静态构造函数之前立即运行。
  • 在第一个实例构造函数调用或第一个静态成员引用时调用静态构造函数。
  • 该函数参数按从左到右的顺序进行评估。

依赖它是一个非常糟糕的主意,因为它可能会使任何阅读您的代码的人感到困惑,特别是如果他们熟悉具有类似语法的语言,而这些语言并不能满足上述所有四个保证。

请注意,Porges 评论与我最初的声明(基于 .NET 行为)有关,即保证太弱,无法确保观察到的行为。Porges 是对的,保证足够强大,但实际上涉及的链条比他建议的要复杂得多。

于 2010-09-09T22:47:27.263 回答
6

您可能有兴趣知道,甚至可以在默认初始化和变量初始化之间为字段赋值。

private static int b = Foo();
private static int a = 4;

private static int Foo()
{
    Console.WriteLine("{0} - Default initialization", a);
    a = 3;
    Console.WriteLine("{0} - Assignment", a);
    return 0;
}

public static void Main()
{
    Console.WriteLine("{0} - Variable initialization", a);
}

输出

0 - Default initialization
3 - Assignment
4 - Variable initialization
于 2017-11-27T03:19:58.390 回答
-2

确实可以保证静态成员的确定性初始化......但它不是“文本顺序”。此外,它可能不会以完全惰性的方式执行(意味着仅在首次引用静态变量时)。但是,在您使用整数的示例中,它不会有所作为。

在某些情况下,需要进行延迟初始化(尤其是使用昂贵的单例)——在这种情况下,您有时必须跳过一些障碍才能使其正确。

于 2010-09-09T22:40:54.163 回答