发现一个很诡异的现象,我写了代码,大致如下,主要是想让AAA类在全局中只能创建一个实例,不过new AAA的时候要做一些业务逻辑,用到了其他类的静态成员的数据 ,所以我在使用AAA.Instance之前就给BBB.data赋值了,看上去没问题,但是报错了,因为静态字段的初始化时间点出乎了我的预料
我发现,AAA.Instance并不是在我认为第一次调用AAA实例或者静态成员时创建,而是在“之前”初始化,这个“之前”,看上去像是刚进入到Test2方法时这个时间点
更诡异的是,如果我声明下AAA的静态构造函数,貌似就没有问题了,这是什么原因啊,这个通过声明静态构造函数来规范静态字段初始化时间点的方法稳定不,我要不要在静态构造函数中初始化Instance字段啊,感觉这样更靠谱一些
这段代码直接运行是报错的
public static class Program {
static void Main(string[] args) {
Test1();
Test2();
Console.ReadKey();
}
static void Test1() {
//BBB.data = new List<string>() { "sss" }; //如果不在同一个方法内就没有问题,估计“之前”就是进入到Test2方法时
Console.WriteLine("111");
}
static void Test2() {
BBB.data = new List<string>() { "sss" }; //感觉进入方法时就立刻创建Instance了,这个时候还没执行到给BBB.data赋值
Console.WriteLine("222");
AAA.Instance.Test();
}
}
internal class AAA {
public readonly static AAA Instance = new AAA();
//static AAA() { } //当类有静态构造函数时,就按照预期的顺序执行了,否则就会在进入方法时立刻执行new AAA()
private AAA() {
Console.WriteLine("create AAA");
foreach (var d in BBB.data) {
Console.WriteLine(d); //如果没有静态构造函数,即使在AAA.Instance之前给BBB.data赋值,依然会报错
}
}
public void Test() {
Console.WriteLine("Test");
}
}
internal class BBB {
public static List<string> data;
}
这是个好问题,属于写十年程序都遇不到的问题。
你的直接问题:
通过声明静态构造函数来规范静态字段初始化时间点的方法稳定不,我要不要在静态构造函数中初始化Instance字段啊,感觉这样更靠谱一些
回答:
稳定。
因为,静态构造函数的存在将防止添加 BeforeFieldInit 类型属性,这将限制运行时优化。
没有静态构造函数的存在,你的类将在编译后增加了BeforeFieldInit 类型属性, 加了这个类型属性的意思是:指定调用此类型的静态方法并不强制系统初始化此类型。
简单就是加了此flag之后,运行时会优化代码,类型的静态方法(字段)可能在第一次使用前就被调用,而这个时间点不定。
看下你代码的IL,AAA和BBB类都加上了该flag。
参考:
官方文档:
https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/static-constructors
《深入理解C#》作者关于此问题的讨论和文章:
https://stackoverflow.com/questions/610818/what-does-beforefieldinit-flag-do
https://csharpindepth.com/articles/BeforeFieldInit
https://csharpindepth.com/Articles/Singleton
非常感谢,学习了
按上面的写法,应该就能尽情玩耍了。