对于一个类的静态变量何时初始化,大家都有一个普遍的共识,那就是第一次使用该类时,初始化该类的所有静态变量和静态方法。
/// <summary>
/// 只有在第一次使用到Test1的时候,才会初始化Test1.x
/// </summary>
class Test1
{
public static string x = EchoAndReturn("In type initializer");
public static string EchoAndReturn(string s)
{
Console.WriteLine(s);
return s;
}
}
但是当我看到单例模式的时候,却看到一个矛盾,那就是饿汉模式下的单例模式,不是懒加载。
/// <summary>
/// 饿汉单例模式,无论是否调用,都会占用系统资源
/// </summary>
public class HungrySingleton
{
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton()
{
}
public static HungrySingleton getInstance()
{
return instance;
}
}
带着这个疑问,我继续探究下去,到网上查到了基于静态内部类的单例模式。网上的说法是,该方式能够实现延迟加载。
public class Singleton6
{
private Singleton6() { }
private static class SingletonInstance
{
public static Singleton6 Instance = new Singleton6();
}
public static Singleton6 Instance()
{
return SingletonInstance.Instance;
}
}
如果说静态成员是在类第一次被使用时进行初始化,那饿汉模式和静态内部类的方式并没有区别,都会实现延迟加载。
带着这个疑问,我进行了实验,发现了一个神奇的现象
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting Main");
Test1.EchoAndReturn("Echo!");
Console.WriteLine("After echo");
//Reference a static field in Test
string y = Test1.x;
//Use the value just to avoid compiler cleverness
if (y != null)
{
Console.WriteLine("After field access");
}
Console.ReadKey();
}
}
/// <summary>
/// 理论上只有在第一次使用到Test1的时候,才会初始化Test1.x
/// 也就是In type initializer出现在Starting Main之后
/// </summary>
class Test1
{
public static string x = EchoAndReturn("In type initializer");
public static string EchoAndReturn(string s)
{
Console.WriteLine(s);
return s;
}
}
按理来说,In type initializer这句话应该是在Starting Main之后才会出现,却出现在了最前面,难道说静态成员不是在类第一次被使用时进行初始化,而是在程序启动时就初始化?
查阅了相关资料之后,终于找到了问题的答案,那就是类都有一个隐藏属性beforefieldinit。
beforefieldinit,JIT编译器可以在首次访问一个静态字段或者一个静态/实例方法之前,或者创建类型的第一个实例之前,随便找一个时间生成调用。具体调用时机由CLR决定,它只保证访问成员之前会执行(隐式)静态构造函数,但可能会提前很早就执行。
也就是说静态成员会在类第一次使用之前的任何时间初始化(由CLR智能决定),如果是这样的话,静态内部类的方式岂不是解决不了延迟加载的问题?
有办法,那就是在类中实现静态构造函数,那beforefieldinit属性就会被precise属性替换,确保静态成员会在类第一次使用之前的那一刻进行初始化。
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting Main");
Test2.EchoAndReturn("Echo!");
Console.WriteLine("After echo");
//Reference a static field in Test
string y = Test2.x;
//Use the value just to avoid compiler cleverness
if (y != null)
{
Console.WriteLine("After field access");
}
Console.ReadKey();
}
}
/// <summary>
/// 只有在第一次使用到Test1的时候,才会初始化Test1.x
/// </summary>
class Test1
{
public static string x = EchoAndReturn("In type initializer");
public static string EchoAndReturn(string s)
{
Console.WriteLine(s);
return s;
}
}
class Test2
{
public static string x = EchoAndReturn("In type initializer");
// Defines a parameterless constructor.
static Test2()
{
}
public static string EchoAndReturn(string s)
{
Console.WriteLine(s);
return s;
}
}
按照这种方式确实解决了静态成员在类第一次使用之前的那一刻进行初始化的问题。但是网上能够找到的静态内部类单例模式普遍都有一个问题,那就是没有实现静态内部类的静态构造函数,结果就是和饿汉模式一样,没有解决延迟加载问题,最后贴上正确的静态内部类单例模式
/// <summary>
/// 静态内部类单例模式,线程安全
/// </summary>
public class StaticSingleton
{
private class InnerInstance
{
/// <summary>
/// 当一个类有静态构造函数时,它的静态成员变量不会被beforefieldinit修饰
/// 就会确保在被引用的时候才会实例化,而不是程序启动的时候实例化
/// </summary>
static InnerInstance() { }
internal static StaticSingleton instance = new StaticSingleton();
}
private StaticSingleton()
{
}
public static StaticSingleton getInstance()
{
return InnerInstance.instance;
}
}