在.net 4+中,lazy是实现单例最稳妥的选择,因其线程安全、延迟初始化且代码简洁;手动dcl易出错且不必要,静态字段初始化非真正延迟,di容器应为首选方案。

直接说结论:在 .NET 4+ 环境下,用 Lazy<t></t> 是最稳妥的选择——线程安全、延迟初始化、代码干净,没有隐藏陷阱。
为什么不用双重检查锁定(DCL)
很多人从 Java 转过来,第一反应是写 if (_instance == null) { lock (...) { if (_instance == null) ... } }。但在 C# 里,它容易出错,且没必要:
-
volatile忘加会导致指令重排,多个线程仍可能创建多个实例 -
lock对象若误用this或typeof(Singleton),会引发死锁或类型级锁竞争 - 即使写对了,每次调用
Instance都要走两次判空 + 潜在的 lock 开销,性能不如Lazy<t></t> - .NET Framework 4.0 之后完全没必要手写 DCL——
Lazy<t></t>底层已用更优方式实现
Lazy<t></t> 的正确写法和常见误用
核心就三行,但细节决定是否真线程安全:
public sealed class Singleton
{
private static readonly Lazy<singleton> _instance = new Lazy<singleton>(() => new Singleton());
public static Singleton Instance => _instance.Value;
private Singleton() { }
}</singleton></singleton>
注意点:
- 必须用
readonly修饰_instance字段,否则外部可能替换整个Lazy<t></t>实例 - 构造函数必须是
private,且类建议加sealed(防继承后绕过单例) - 不要把
new Lazy<singleton>(() => new Singleton())</singleton>写在属性 get 里——那会每次新建Lazy实例,彻底失效 - 如果构造函数需抛异常,
_instance.Value第一次访问时会缓存该异常,后续再取仍抛同一异常(这是Lazy<t></t>行为,不是 bug)
静态字段初始化 vs Lazy<t></t>:延迟性差异很关键
下面这种写法看似简洁,但不是“懒”的:
private static readonly Singleton _instance = new Singleton();
它属于“饿汉式”,类第一次被任何静态成员触发时(哪怕只是访问另一个无关的 public static int X = 1;),_instance 就会被初始化。这意味着:
- 如果单例构造开销大(如加载配置、连数据库),而实际从未调用
Instance,纯属浪费 - 若构造函数依赖尚未初始化的其他静态资源,可能引发
TypeInitializationException - 单元测试中难以 mock 或控制初始化时机
而 Lazy<t></t> 真正做到了“首次访问 Value 才执行工厂函数”,触发时机可控、可预测。
DI 容器才是现代 C# 的默认解法
如果你在 ASP.NET Core 或使用 Microsoft.Extensions.DependencyInjection,别手动写单例类:
- 注册时用
services.AddSingleton<isingletonservice singletonservice>()</isingletonservice>,容器自动保证单例生命周期 - 构造函数注入替代全局静态访问,解耦、可测、支持接口抽象
- 静态
Instance属性本质是全局状态,会阻碍并行测试、多租户隔离、模块热重载等场景 - 只有极少数情况绕不开静态单例:比如日志器(
ILogger)的静态门面类、或跨 DI 容器边界的底层组件
真正难的从来不是“怎么写一个单例”,而是“要不要写”——多数时候,答案是否定的。










