41、C#什么是单例设计模式

单例设计模式(Singleton Pattern) 是一种创建型设计模式,它的核心目标是:

确保一个类在整个应用程序生命周期中,有且仅有一个实例,并提供一个全局访问点。


✅ 为什么需要单例?

有些对象天然只需要一个,比如:

  • 日志记录器(Logger)
  • 配置管理器(ConfigManager)
  • 数据库连接池
  • 线程池
  • 应用程序的主窗口(某些桌面应用)

如果允许多个实例:

  • 可能导致资源浪费(如重复读取配置文件)
  • 引发状态不一致(如多个 Logger 写同一个文件冲突)
  • 违反业务逻辑(如"系统设置"只能有一份)

👉 单例就是为了解决这些问题。


✅ 单例的核心要素

要实现真正的单例,必须满足 3 个条件

  1. 私有构造函数 → 防止外部 new
  2. 静态私有字段 → 保存唯一实例
  3. 公共静态属性/方法 → 提供全局访问点

✅ C# 中的单例实现方式(从简单到线程安全)

方式 1️⃣:懒汉式(Lazy,非线程安全)------ ❌ 不推荐

复制代码
public class Singleton
{
    private static Singleton _instance;
    
    private Singleton() { } // 私有构造函数

    public static Singleton Instance
    {
        get
        {
            if (_instance == null)
                _instance = new Singleton();
            return _instance;
        }
    }
}

⚠️ 问题:多线程下可能创建多个实例!


方式 2️⃣:饿汉式(Eager)------ ✅ 简单安全,但可能浪费资源

复制代码
public class Singleton
{
    private static readonly Singleton _instance = new Singleton();
    
    private Singleton() { }

    public static Singleton Instance => _instance;
}

优点 :线程安全(静态字段在类型加载时初始化)

缺点:即使不用也会创建实例(不支持懒加载)


方式 3️⃣:双重检查锁(Double-Checked Locking)------ ✅ 经典线程安全

复制代码
public class Singleton
{
    private static volatile 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;
        }
    }
}

优点 :线程安全 + 懒加载

⚠️ 注意 :必须加 volatile 防止指令重排序(.NET 中有时可省略,但建议保留)


方式 4️⃣:推荐!使用 Lazy<T>(C# 4+)------ ✅ 最简洁安全

复制代码
public class Singleton
{
    private static readonly Lazy<Singleton> _instance = 
        new Lazy<Singleton>(() => new Singleton());

    private Singleton() { }

    public static Singleton Instance => _instance.Value;
}

优点

  • 线程安全(Lazy<T> 内部已处理)
  • 懒加载
  • 代码简洁
  • 支持异常处理和自定义初始化逻辑

⚠️ 单例的常见陷阱

问题 说明
反射破坏 通过反射可调用私有构造函数 → 可加 if (_instance != null) throw 防御
序列化破坏 反序列化可能创建新实例 → 实现 ISerializable 或使用 [NonSerialized]
多线程安全 必须确保初始化过程线程安全(见上文)
单元测试困难 全局状态难以 mock → 可考虑依赖注入替代

🆚 单例 vs 静态类

特性 单例 静态类
可继承/实现接口 ✅ 可以 ❌ 不可以
延迟初始化 ✅ 可以 ❌ 静态字段在类型加载时初始化
支持多态 ✅ 可以 ❌ 不行
单元测试友好度 中等(可通过接口解耦) 差(无法 mock)
内存位置 托管堆 静态区

💡 如果你只需要工具方法(无状态),用静态类

如果你需要状态 + 全局唯一 + 可扩展 ,用单例


✅ 现代替代方案:依赖注入(DI)

ASP.NET Core 等现代框架中,更推荐用 DI 容器注册单例,而非手写单例:

复制代码
// 注册
services.AddSingleton<ILogger, FileLogger>();

// 使用(通过构造函数注入)
public class MyService
{
    private readonly ILogger _logger;
    public MyService(ILogger logger) => _logger = logger;
}

优势

  • 解耦
  • 易于测试
  • 生命周期由容器管理
  • 避免全局状态污染

📌 建议:在新项目中优先考虑 DI,仅在无法使用 DI 的场景(如旧系统、工具类库)使用传统单例。


✅ 小结

关键点 说明
目的 确保全局只有一个实例
三要素 私有构造函数 + 静态实例 + 全局访问点
推荐实现 Lazy<T> 或 DI 容器
慎用场景 避免滥用(不是所有"全局"都需要单例)

🧠 一句话记住
"单例 = 全局唯一实例,懒加载 + 线程安全是关键。"


问题

什么是全局状态

它是可以从应用程序任何位置访问的状态

为什么单例是一种反模式

因为它是一部分全局状态,而全局状态很难控制。

单例设计模式和应用程序单例有什么区别

应用程序单例只是在应用程序各处使用的单一对象,他不会强制其单例性,而单例设计模式会。

相关推荐
夏霞2 小时前
c# ASP.NET Core SignalR 客户端与服务端自动重连配置指南
开发语言·c#·asp.net
ZHE|张恒2 小时前
设计模式实战篇(六):装饰器模式 —— 让系统具备“可生长能力”的架构思想
设计模式·装饰器模式
Scout-leaf2 小时前
九成九新自用C#入门文档
c#
皮皮林5512 小时前
别再只会 mvn install 了!深入拆解 Maven 插件核心原理
java·maven
百***49002 小时前
SpringSecurity的配置
java
@老蝴3 小时前
Java EE - 常见的死锁和解决方法
java·开发语言·java-ee
wangmengxxw3 小时前
Swagger技术
java·swagger
全干engineer3 小时前
idea拉取github代码 -TLS connect error 异常问题
java·github·intellij-idea
10岁的博客4 小时前
二维差分算法高效解靶场问题
java·服务器·算法