C#23种设计模式-单例模式(Singleton)详解与应用

        如果你希望深入了解 C# 中单例模式的详细实现、不同写法的优缺点以及实际应用场景,本文会从核心定义、多种实现方式(从简单到线程安全)、使用注意事项和真实业务场景四个维度,帮你彻底掌握单例模式。
一、单例模式核心定义
单例模式是创建型设计模式,核心目标是:
保证一个类在整个应用程序生命周期中只有一个实例;
提供一个全局统一的访问入口;
控制实例的创建时机(懒加载 / 饿汉式)。
二、C# 单例模式的多种实现方式(按推荐度排序)
1. 推荐:懒加载 + 线程安全(Lazy<T> 实现)
这是 C# 官方推荐的最优写法,利用System.Lazy<T>类天然实现线程安全和懒加载,代码极简且无坑。

using System;

// sealed 防止子类继承破坏单例
public sealed class SingletonLazy
{
    // 1. 私有静态Lazy实例,泛型参数为当前类,懒加载
    // LazyThreadSafetyMode.ExecutionAndPublication 是默认值,保证线程安全
    private static readonly Lazy<SingletonLazy> _lazyInstance = 
        new Lazy<SingletonLazy>(() => new SingletonLazy());

    // 2. 私有构造函数:禁止外部通过 new 创建实例
    private SingletonLazy()
    {
        // 可添加初始化逻辑,如读取配置、连接数据库等
        Console.WriteLine("SingletonLazy 实例被创建");
    }

    // 3. 公共静态属性:全局访问入口
    public static SingletonLazy Instance => _lazyInstance.Value;

    // 示例业务方法
    public void DoBusiness()
    {
        Console.WriteLine("执行单例对象的业务逻辑");
    }
}

// 测试代码
class Program
{
    static void Main()
    {
        // 第一次访问Instance时,才会创建实例
        var instance1 = SingletonLazy.Instance;
        var instance2 = SingletonLazy.Instance;

        // 验证是否为同一个实例(输出 True)
        Console.WriteLine($"是否为同一实例:{ReferenceEquals(instance1, instance2)}");
        
        instance1.DoBusiness();
    }
}

关键说明

  • Lazy<T> 是.NET 4.0+ 引入的类,默认线程安全,多个线程同时访问Value时,只会创建一次实例;
  • sealed 修饰类:防止子类继承后重写构造函数,破坏单例;
  • 懒加载特性:实例直到第一次调用Instance时才会创建,节省内存(适合初始化成本高的对象)。
2. 基础版:饿汉式(非懒加载)

最简单的写法,类加载时就创建实例,天生线程安全,但无法懒加载。

public sealed class SingletonEager
{
    // 类初始化时直接创建实例(饿汉式:提前加载)
    private static readonly SingletonEager _instance = new SingletonEager();

    private SingletonEager() { }

    public static SingletonEager Instance => _instance;
}

优缺点

  • ✅ 优点:代码简单、线程安全(CLR 保证静态字段初始化的线程安全);
  • ❌ 缺点:无法懒加载,即使程序全程不使用该实例,也会创建(浪费内存);
  • 适用场景:实例初始化成本极低、必须提前加载的场景(如简单的配置管理器)。
3. 经典版:双重检查锁定(Double-Check Locking)

这是传统多线程场景的写法,需手动处理线程安全,适合.NET 4.0 之前的版本(现在推荐用Lazy<T>替代)。

public sealed class SingletonDoubleCheck
{
    // volatile 关键字:防止编译器优化导致的指令重排,保证线程可见性
    private static volatile SingletonDoubleCheck _instance;
    // 锁对象:保证线程同步
    private static readonly object _lockObj = new object();

    private SingletonDoubleCheck() { }

    public static SingletonDoubleCheck Instance
    {
        get
        {
            // 第一次检查:避免每次获取实例都加锁(提升性能)
            if (_instance == null)
            {
                // 加锁:保证同一时间只有一个线程进入
                lock (_lockObj)
                {
                    // 第二次检查:防止多个线程等待锁后重复创建实例
                    if (_instance == null)
                    {
                        _instance = new SingletonDoubleCheck();
                    }
                }
            }
            return _instance;
        }
    }
}

关键注意事项

  • 必须加volatile关键字:否则在多线程下,可能出现 “实例已赋值但未完成初始化” 的问题;
  • 锁对象必须是private static readonly:避免外部修改,保证锁的唯一性。

三、单例模式的常见坑与避坑指南

  1. 避免反射破坏单例:反射可以调用私有构造函数创建新实例,解决方式:在构造函数中加判断,若已有实例则抛异常。

    private SingletonLazy()
    {
        if (_lazyInstance.IsValueCreated)
        {
            throw new InvalidOperationException("单例类不允许创建多个实例");
        }
    }

    }
    避免序列化破坏单例:
    序列化和反序列化会创建新实例,解决方式:重写GetObjectData方法,或添加[Serializable]并实现IObjectReference接口返回单例实例。
    不要滥用单例:
    单例本质是 “全局变量”,过度使用会导致:
    代码耦合度高(依赖单例的类难以单元测试);
    实例常驻内存,无法释放(适合无状态对象,如日志、配置)。
    四、单例模式的实际应用场景
    1. 配置管理器
    全局唯一读取配置文件,避免重复加载配置:

    public sealed class ConfigManager
    {
        private static readonly Lazy<ConfigManager> _instance = new Lazy<ConfigManager>(() => new ConfigManager());
        
        // 配置数据
        public string DbConnectionString { get; private set; }
        public int MaxRetryCount { get; private set; }
    
        private ConfigManager()
        {
            // 从配置文件/环境变量加载配置
            DbConnectionString = System.Configuration.ConfigurationManager.AppSettings["DbConnection"];
            MaxRetryCount = int.Parse(System.Configuration.ConfigurationManager.AppSettings["MaxRetryCount"]);
        }
    
        public static ConfigManager Instance => _instance.Value;
    }
    
    // 使用
    var dbConn = ConfigManager.Instance.DbConnectionString;
    2. 日志管理器

    全局唯一的日志对象,保证日志写入的顺序和一致性:

    public sealed class LogManager
    {
        private static readonly Lazy<LogManager> _instance = new Lazy<LogManager>(() => new LogManager());
        private readonly StreamWriter _logWriter;
    
        private LogManager()
        {
            // 初始化日志文件写入器
            _logWriter = new StreamWriter("app.log", true, Encoding.UTF8);
        }
    
        public static LogManager Instance => _instance.Value;
    
        // 写日志方法
        public void Log(string message)
        {
            lock (_logWriter) // 保证多线程写入安全
            {
                _logWriter.WriteLine($"[{DateTime.Now}] {message}");
                _logWriter.Flush();
            }
        }
    }
    
    // 使用
    LogManager.Instance.Log("用户登录成功");
    3. 数据库连接池

    全局唯一管理数据库连接,避免频繁创建 / 销毁连接:

    public sealed class DbConnectionPool
    {
        private static readonly Lazy<DbConnectionPool> _instance = new Lazy<DbConnectionPool>(() => new DbConnectionPool());
        private readonly List<SqlConnection> _connections = new List<SqlConnection>();
    
        private DbConnectionPool()
        {
            // 初始化连接池
            for (int i = 0; i < 10; i++)
            {
                var conn = new SqlConnection(ConfigManager.Instance.DbConnectionString);
                conn.Open();
                _connections.Add(conn);
            }
        }
    
        public static DbConnectionPool Instance => _instance.Value;
    
        // 获取连接
        public SqlConnection GetConnection()
        {
            lock (_connections)
            {
                var conn = _connections.FirstOrDefault(c => c.State == ConnectionState.Open);
                return conn ?? throw new InvalidOperationException("连接池无可用连接");
            }
        }
    }

    总结

  2. 最优实现:C# 中优先使用Lazy<T>实现单例,兼顾线程安全、懒加载和代码简洁性;
  3. 核心原则:私有构造函数 + 静态全局访问点 + 控制实例唯一性;
  4. 适用场景:无状态、需全局唯一访问、初始化成本高的对象(配置、日志、连接池等),避免用于有状态的业务对象。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

工业程序猿老赵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值