26

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

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

没有做任何特别有效的事情。这只是运气吗?C# 是否有解决此问题的规则?

编辑:(回复:Panos)在文件中的词法顺序似乎是王道?跨文件呢?

在寻找时,我尝试了这样的循环依赖:

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

并且程序运行不一样(测试套装全面失败,我没有进一步研究)。

4

4 回答 4

22

有关规则,请参阅C# 规范的第 10.4 节

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

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

这些规则与 Java 不同(请参阅 JLS 的第 8.3.2.3 节,了解 Java 关于前向引用的规则,这些规则更具限制性)。

于 2008-10-08T23:59:33.113 回答
15

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

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会导致静态构造函数中的异常,并使用默认值(null)A留下字段。a由于ais nullb也不能正确初始化。

如果我们没有循环依赖,那么一切正常。


编辑:以防万一您没有阅读评论,Jon Skeet提供了一个非常有趣的阅读:静态构造函数和类型初始化程序之间的区别

于 2008-10-08T23:54:31.127 回答
2

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

static private List<int> a;
static private List<int> b;

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

这样您就不必猜测发生了什么,并且您的意图很清楚。

于 2008-10-08T23:59:13.130 回答
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] };
}
于 2008-10-08T23:58:26.847 回答