代码之家  ›  专栏  ›  技术社区  ›  BCS

C中静态构造函数/初始值设定项的顺序#

  •  18
  • BCS  · 技术社区  · 16 年前

    在使用C应用程序时,我注意到在一些地方静态初始值设定项相互依赖,比如:

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

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

    编辑: (回复:帕诺斯)在一个文件中,词汇顺序似乎是国王?跨文件怎么样?

    我试过这样的循环依赖:

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

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

    4 回复  |  直到 10 年前
        1
  •  14
  •   Community CDub    7 年前

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

    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 , b 也不能正确初始化。

    如果我们没有周期性的依赖关系,一切都会正常工作。


    编辑:以防万一你没有阅读评论, Jon Skeet 提供了一个非常有趣的阅读: The differences between static constructors and type initializers .

        2
  •  16
  •   Cowan    16 年前

    section 10.4 of the C# spec 对于这里的规则:

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

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

    这些规则与Java的不同(参见 section 8.3.2.3 of the JLS 对于Java的前向引用规则,这是更具限制性的。

        3
  •  2
  •   Bryant    16 年前

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

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

    那么你就不必去猜测发生了什么,你的意图也很清楚。

        4
  •  0
  •   Sam Saffron James Allen    16 年前

    是的,你很幸运。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] };
    }