目录

C# 单例模式 (Singleton Pattern)

单例模式是 C# 开发中最常用的设计模式之一。它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。

1. 为什么使用单例模式?

在 C# 应用程序中,单例模式通常用于管理共享资源,例如:

2. 实现单例模式的关键点

要实现一个标准的单例类,必须满足以下条件:

  1. 私有构造函数:防止外部通过 `new` 关键字创建实例。
  2. 私有静态变量:用于持有类的唯一实例。
  3. 公共静态方法/属性:提供全局访问点(通常命名为 `Instance`)。

3. C# 中的几种实现方式

在 C# 中实现单例有多种方式,主要区别在于线程安全性延迟加载 (Lazy Loading) 的处理。

3.1. 简单的线程不安全实现 (Bad)

这是最基本的实现,但在多线程环境下是不安全的。如果两个线程同时判断 `_instance == null`,可能会创建两个实例。

public class Singleton
{
    private static Singleton _instance;
 
    // 私有构造函数
    private Singleton() { }
 
    public static Singleton Instance
    {
        get
        {
            // 线程不安全!
            if (_instance == null)
            {
                _instance = new Singleton();
            }
            return _instance;
        }
    }
}

3.2. 简单的线程安全实现 (加锁)

通过 `lock` 关键字确保同一时间只有一个线程执行创建实例的代码。

public class Singleton
{
    private static Singleton _instance;
    private static readonly object _lock = new object();
 
    private Singleton() { }
 
    public static Singleton Instance
    {
        get
        {
            // 每次访问都会加锁,性能较差
            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = new Singleton();
                }
                return _instance;
            }
        }
    }
}

3.3. 双重检查锁定 (Double-Check Locking)

这种方式既保证了线程安全,又避免了每次访问都加锁带来的性能损耗。只有在实例未创建时才加锁。

public class Singleton
{
    private static Singleton _instance;
    private static readonly object _lock = new object();
 
    private Singleton() { }
 
    public static Singleton Instance
    {
        get
        {
            // 第一次检查:如果已经创建,直接返回,避免加锁
            if (_instance == null)
            {
                lock (_lock)
                {
                    // 第二次检查:防止多个线程同时通过了第一次检查
                    if (_instance == null)
                    {
                        _instance = new Singleton();
                    }
                }
            }
            return _instance;
        }
    }
}

3.4. 静态初始化 (饿汉式)

利用 .NET CLR 的特性,静态构造函数只会执行一次。这种方式非常简单且线程安全,但缺点是不支持延迟加载(类一加载,实例就被创建,即使你还没用到它)。

public sealed class Singleton
{
    // CLR 保证了静态字段初始化的线程安全性
    private static readonly Singleton _instance = new Singleton();
 
    // 显式静态构造函数告诉 C# 编译器不要将类型标记为 BeforeFieldInit
    static Singleton() { }
 
    private Singleton() { }
 
    public static Singleton Instance
    {
        get
        {
            return _instance;
        }
    }
}

3.5. 使用 System.Lazy<T> (推荐)

在 .NET 4.0 及更高版本中,这是最推荐的实现方式。它利用 `System.Lazy<T>` 类型自动处理线程安全和延迟加载,代码简洁且高效。

public sealed class Singleton
{
    // Lazy<T> 默认是线程安全的
    private static readonly Lazy<Singleton> _lazy = 
        new Lazy<Singleton>(() => new Singleton());
 
    private Singleton() { }
 
    public static Singleton Instance 
    { 
        get 
        { 
            return _lazy.Value; 
        } 
    }
}

4. 总结

实现方式 线程安全 延迟加载 复杂度 推荐指数
简单实现 No Yes 不推荐
简单加锁 Yes Yes 一般
双重检查锁定 Yes Yes 一般
静态初始化 Yes No 推荐 (如果不需要延迟加载)
System.Lazy<T> Yes Yes 强烈推荐

5. 注意事项

  1. 单一职责原则:单例类除了管理自己的实例外,通常还承担具体的业务功能。要注意不要让单例类变得过于臃肿。
  2. 测试困难:单例模式通常持有全局状态,这使得单元测试变得困难(难以模拟和重置状态)。在需要高度可测试性的系统中,通常建议使用依赖注入 (Dependency Injection) 容器来管理单例的生命周期,而不是手动实现单例模式。