在研究用C#构建Singleton的最佳方法时,我偶然发现了以下文章,其中简要提到了在C++中
"C++规范在初始化方面留下了一些歧义静态变量的顺序。"
我最终调查了这个问题,发现了这个和这个。基本上(据我所知),C++中静态变量的初始化顺序是未定义的。好吧,我想到目前为止还不错,但后来我想理解以下声明,即文章后来制作
"幸运的是,.NET框架通过其变量初始化的处理。"
所以我找到了这个页面,上面写着
类的静态字段变量初始值设定项对应于中按文本顺序执行的分配序列它们出现在类声明中。
并给出的例子
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:完全独立于类声明的位置或顺序。然而,根据我对这篇文章的解释,它说这是这些类的声明顺序的结果,我的测试没有支持这一点?
有人能为我澄清这一点(以及文章试图表达的观点)吗?也许可以提供一个更好的例子,让我理解所描述的行为?
类的静态字段变量初始值设定项对应于中按文本顺序执行的分配序列它们出现在类声明中。
这意味着在同一个类中,静态字段按照源代码中出现的顺序进行初始化。例如:
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);
}
假设A
和B
都没有被静态初始化,则评估顺序保证B
的所有字段将在A
的任何字段之前被初始化。每个类的字段将按照第一条规则指定的顺序进行初始化。
为了讨论的目的,我忽略了beforefieldinit
的存在。
在C++中,在单个翻译单元中具有静态存储持续时间的变量的初始化顺序是这些变量的定义发生的顺序。未指定具有静态存储持续时间的变量在不同转换单元之间的初始化顺序。
也就是说,C++标准确实提供了与您引用的内容类似的保证,用类中的声明顺序代替定义此类变量的单个翻译单元中的定义顺序。但这并不是重要的区别。
而在C++中,这是唯一的保证,而在C#中,有一个额外的保证,即所有静态成员都将在首次使用类之前初始化。这意味着,如果您的程序依赖于A
(考虑不同程序集中的每种类型,这是最坏的情况),它将开始初始化A
中的所有静态字段,如果A
反过来依赖于任何静态初始化的B
,那么B
静态成员的初始化将在那里触发。
与C++相比,在静态初始化[*]期间,所有其他具有静态持续时间的变量都被假设被初始化。这是主要的区别:C++假设它们已经初始化,C#确保它们在使用之前。
[*]从技术上讲,这是有问题的情况可能是标准中的动态初始化。每个翻译单元内具有静态存储持续时间的变量的初始化是一个两步过程,其中在第一次静态初始化中将变量设置为固定的常量表达式,随后在第二次dynamic中初始化初始化器不是常量表达式的所有具有静态存储的变量。