Lazy是.NET 4.0+最推荐的单例实现方式,线程安全、延迟初始化、代码简洁;需用readonly声明静态字段、private构造函数和sealed类;饿汉式适用于创建开销小且必用场景;序列化会破坏单例,需显式干预。

直接说结论:Lazy<t></t> 是当前 .NET(4.0+)下最推荐的单例实现方式——线程安全、延迟初始化、代码简洁,且无需手动处理 volatile 或 lock 细节。
为什么不用双重检查锁定(DCL)
很多人从老资料里抄来 Double-Check Locking 写法,但实际它容易出错:漏写 volatile 修饰字段会导致指令重排,lock 对象若误用 this 或类型本身会引发死锁或竞争。即使写对,每次调用 Instance 属性仍要走两次判空 + 潜在锁开销。而 Lazy<t></t> 底层已由 CLR 保证原子性与内存可见性,你只管写 _instance.Value,其余交给框架。
Lazy<t></t> 的正确写法与关键点
必须用 readonly 声明静态字段,否则可能被意外赋值;构造函数必须 private,否则破坏单例语义;类建议加 sealed,避免子类绕过控制。
private static readonly Lazy<singleton> _instance = new Lazy<singleton>(() => new Singleton());</singleton></singleton>public static Singleton Instance => _instance.Value;- 不建议在 lambda 中做耗时初始化逻辑(如 IO、网络),因为首次访问
Value时才执行,可能阻塞调用线程 - 如果需要异常传播可控,可传入
LazyThreadSafetyMode.ExecutionAndPublication(默认值,已足够)
静态字段直接初始化(饿汉式)的适用场景
当实例创建开销极小、且确定程序启动后一定会用到时,可用静态字段直连初始化。它比 Lazy<t></t> 少一次委托调用,但牺牲了延迟性——哪怕你从不访问 Instance,实例也在类型首次被引用时创建。
private static readonly Singleton _instance = new Singleton();public static Singleton Instance => _instance;- 没有静态构造函数也行;显式写
static Singleton() { }仅用于调试或插入初始化日志 - 不适用于依赖 DI 容器注入依赖项的场景(因为构造函数无法接收参数)
序列化和反序列化会破坏单例?
是的。默认 BinaryFormatter(已弃用)或自定义序列化若不干预,反序列化会生成新对象。解决方法是让类实现 ISerializable,并在 GetObjectData 和反序列化构造函数中强制返回现有 Instance。但更现实的做法是:标记类为 [Serializable] 后直接抛异常,或改用 JSON 序列化(不触发构造函数,需配合 [JsonConstructor] 控制)。
真正难处理的是跨 AppDomain 或跨进程场景——这时候单例已不是语言层面能兜住的问题,得靠外部协调机制(如命名互斥体、分布式锁)。


















