【问题标题】:Ambiguity in initialization order of static variables静态变量的初始化顺序不明确
【发布时间】:2012-07-09 09:50:38
【问题描述】:

在我研究在 C# 中构建单例的最佳方法期间,我偶然发现了以下 article,其中简要提到了 C++

“C++ 规范在初始化方面留下了一些歧义 静态变量的顺序。”

我最终调查了这个问题并找到了thisthis。基本上重点(据我所知)是 C++ 中静态变量的初始化顺序是未定义的。好吧我想到目前为止还不错,但是后来我想理解这篇文章稍后做出的以下陈述

“幸运的是,.NET Framework 通过它的 变量初始化的处理。”

所以我找到了this他们说的页面

类的静态字段变量初始化器对应于 以文本顺序执行的任务序列 它们出现在类声明中。

并举个例子

using System;
class Test
{
   static void Main() {
      Console.WriteLine("{0} {1}", B.Y, A.X);
   }
   public static int F(string s) {
      Console.WriteLine(s);
      return 1;
   }
}
class A
{
   static A() {}
   public static int X = Test.F("Init A");
}
class B
{
   static B() {}
   public static int Y = Test.F("Init B");
}

the output must be: 
Init B 
Init A
1 1

"因为静态构造函数何时执行的规则(定义在 第 10.11 节)提供 B 的静态构造函数(因此 B 的 静态字段初始值设定项)必须在 A 的静态构造函数之前运行,并且 字段初始化器。”

但我感到困惑的是,我的理解是这些示例中静态变量的初始化顺序将基于第一次调用类中的方法或字段的时间,而这又基于代码块(本例从左到右)。 IE:完全独立于类声明的位置或顺序。然而,根据我对那篇文章的解释,它说它是这些类的声明顺序的结果,我的测试没有支持?

有人可以为我澄清这一点(以及文章试图说明的观点),或许可以提供一个更好的例子来说明所描述的行为?

【问题讨论】:

    标签: c# c++ .net initialization initialization-order


    【解决方案1】:

    类的静态字段变量初始化器对应于 以文本顺序执行的任务序列 它们出现在类声明中。

    这意味着在同一个类中,静态字段按照在源代码中出现的顺序进行初始化。例如:

    class A
    {
       public static int X = Test.F("Init A.X");
       public static int Y = Test.F("Init A.Y");
    }
    

    当需要初始化静态字段时,X 保证在 Y 之前初始化。

    "因为静态构造函数何时执行的规则(定义在 第 10.11 节)提供 B 的静态构造函数(因此 B 的 静态字段初始值设定项)必须在 A 的静态构造函数之前运行,并且 字段初始化器。”

    这意味着当访问这些类的表达式出现时,每个类的静态构造函数和成员初始化将按评估顺序运行¹。源代码中类定义的相对出现顺序没有任何作用,即使它们出现在同一个源文件中(他们肯定没有义务这样做)。例如:

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

    假设AB 都没有被静态初始化,评估顺序保证B 的所有字段将在A 的任何字段之前初始化。每个类的字段将按照第一条规则指定的顺序进行初始化。


    ¹出于本次讨论的目的,我忽略了 beforefieldinit 的存在。

    【讨论】:

    • 非常感谢您非常简洁的回答。
    【解决方案2】:

    在 C++ 中,具有静态存储持续时间的变量在单个翻译单元中的初始化顺序是此类变量的定义发生的顺序。未指定具有静态存储持续时间的变量在不同翻译单元之间的初始化顺序。

    也就是说,C++ 标准确实提供了与您引用的内容类似的保证,将类中的声明顺序替换为定义此类变量的单个翻译单元中的定义顺序。但这不是重要的区别。

    虽然在 C++ 中这是唯一的保证,但在 C# 中还有一个附加保证,即所有静态成员都将在第一次使用类之前被初始化。这意味着,如果您的程序依赖于A(考虑不同程序集中的每种类型,这是最坏的情况),它将开始初始化A 中的所有静态字段,如果A 反过来依赖于@ 987654324@ 用于任何这些静态初始化,然后将在那里触发B 静态成员的初始化。

    与 C++ 相比,在静态初始化期间[*],所有其他具有静态持续时间的变量假定被初始化。这是主要区别:C++ 假定它们已初始化,C# 确保它们在使用之前。


    [*] 从技术上讲,有问题的情况可能是标准中的动态 初始化。在每个翻译单元内初始化具有静态存储持续时间的变量是一个两步过程,其中在第一次通过 static 初始化期间将变量设置为固定的常量表达式,然后在第二次通过称为 dynamic 初始化所有具有静态存储且其初始化器不是常量表达式的变量。

    【讨论】:

    • +1 和挑剔:从技术上讲,如果一个类型用beforefieldinit 修饰,那么 CLR 不需要初始化静态成员,直到它们被实际访问(例如,你可以调用你想要的所有静态方法)。实际上,它甚至在访问类之前就初始化了字段。
    猜你喜欢
    • 2010-09-17
    • 2015-06-01
    • 2018-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多