c# 设计模式有哪些 c#单例模式怎么写

来源:这里教程网 时间:2026-02-21 17:37:09 作者:

单例模式在 C# 中不是“只有一种写法”,而是有多个变体,各自适用于不同场景;选错实现方式,轻则线程不安全,重则引发内存泄漏或初始化异常。

为什么不能直接用 static class 替代 Singleton?

static class 看似简单,但它无法实现接口、不能被继承、不能作为泛型约束类型参数,更关键的是——它会在程序集加载时立即初始化所有静态字段,哪怕你根本没用到它。而真正的单例应支持延迟初始化(lazy initialization)。

static class
无法实现
IDisposable
,资源释放不可控
单元测试时难以 mock 或替换依赖 若构造逻辑含副作用(如读配置、连数据库),提前触发会拖慢启动速度

C# 最推荐的单例写法:Lazy + readonly 字段

这是 .NET 4.0+ 下线程安全、延迟加载、简洁可靠的首选方案。CLR 保证

Lazy<t></t>
的初始化是线程安全的,且只执行一次。

public sealed class Logger
{
    private static readonly Lazy<Logger> _instance = new Lazy<Logger>(() => new Logger());
<pre class='brush:php;toolbar:false;'>public static Logger Instance => _instance.Value;
private Logger() { } // 私有构造,防止外部 new

}

Lazy<t></t>
默认使用
LazyThreadSafetyMode.ExecutionAndPublication
,无需额外加锁
构造函数保持
private
,杜绝反射绕过(若需更强防护,可在构造中加
if (Interlocked.Increment(ref _initCount) != 1) throw
不建议在
Instance
getter 中做复杂逻辑,否则每次访问都可能触发隐式开销

什么时候该用双重检查锁定(Double-Checked Locking)?

仅当你需要在 .NET 3.5 或更早版本运行,或必须控制初始化时机(比如要传参给构造函数),才考虑 DCL。它容易写错,常见坑包括:

忘记用
volatile
修饰实例字段,导致其他线程看到未完全构造的对象
lock 对象不是私有静态字段,造成锁粒度失控 在 lock 外部再次判空时,用了非 volatile 字段,引发重排序问题
public sealed class ConfigLoader
{
    private static volatile ConfigLoader _instance;
    private static readonly object _lock = new object();
<pre class='brush:php;toolbar:false;'>public static ConfigLoader Instance
{
    get
    {
        if (_instance == null)
        {
            lock (_lock)
            {
                if (_instance == null)
                    _instance = new ConfigLoader();
            }
        }
        return _instance;
    }
}
private ConfigLoader() { }

}

Async 初始化的单例怎么处理?

如果构造过程需要异步操作(如从文件/网络加载配置),

Lazy<t></t>
不适用(它不支持 async 工厂)。此时应改用
AsyncLazy<t></t>
(需自行实现或引用
Microsoft.VisualStudio.Threading
)。

不要在属性 getter 中直接
await
—— 属性不能是
async
暴露
Task<t></t>
类型的静态成员(如
InstanceAsync
),调用方负责 await
注意首次 await 可能阻塞,后续调用返回已完成 Task

真正难的不是写出一个“能跑”的单例,而是判断它是否该存在——大多数时候,你真正需要的是依赖注入容器(如 Microsoft.Extensions.DependencyInjection)管理生命周期,而不是手写单例。

相关推荐