如果你希望深入了解 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:避免外部修改,保证锁的唯一性。
三、单例模式的常见坑与避坑指南
-
避免反射破坏单例:反射可以调用私有构造函数创建新实例,解决方式:在构造函数中加判断,若已有实例则抛异常。
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("连接池无可用连接"); } } }总结
- 最优实现:C# 中优先使用
Lazy<T>实现单例,兼顾线程安全、懒加载和代码简洁性; - 核心原则:私有构造函数 + 静态全局访问点 + 控制实例唯一性;
- 适用场景:无状态、需全局唯一访问、初始化成本高的对象(配置、日志、连接池等),避免用于有状态的业务对象。
1965

被折叠的 条评论
为什么被折叠?



