在C#项目开发中,单例模式是高频使用的设计模式,传统实现方式需要为每个单例类单独编写实例获取、线程安全控制等逻辑,会产生大量冗余代码。通过泛型单例基类,我们可以把通用的单例逻辑抽象出来,让所有需要单例特性的类只需要继承该基类就能自动获得单例能力,大幅降低重复开发成本。

泛型单例基类的核心设计思路
要实现通用的泛型单例基类,首先需要明确几个核心约束:第一,泛型参数T必须是引用类型,避免值类型带来的装箱拆箱问题;第二,T必须有无参数的构造函数,这样基类才能自动创建实例;第三,需要处理多线程环境下的实例创建安全问题,避免多个线程同时创建实例导致单例失效。
基础泛型单例基类实现
我们先实现线程安全的基础版本,使用lock关键字保证实例创建的原子性,同时通过volatile关键字避免指令重排序带来的问题。
using System;
// 泛型单例基类,约束T为引用类型且无参构造函数
public abstract class SingletonBase<T> where T : class, new()
{
// volatile保证多线程下实例的可见性,避免指令重排
private static volatile T _instance;
// 用于加锁的对象,静态对象保证所有线程共享同一个锁
private static readonly object _lockObj = new object();
// 受保护的构造函数,防止外部直接实例化
protected SingletonBase() { }
// 公开的实例获取属性
public static T Instance
{
get
{
// 第一次检查,避免每次获取实例都加锁,提升性能
if (_instance == null)
{
lock (_lockObj)
{
// 第二次检查,避免多个线程同时通过第一次检查后重复创建实例
if (_instance == null)
{
_instance = new T();
}
}
}
return _instance;
}
}
}
实际使用方式
需要单例的类只需要继承SingletonBase<T>,其中T就是当前类的类型,就可以直接通过Instance属性获取单例实例。
// 示例单例类,继承泛型单例基类
public class GameManager : SingletonBase<GameManager>
{
// 受保护的构造函数,和基类约束匹配
protected GameManager() { }
// 自定义的业务方法
public void InitGame()
{
Console.WriteLine("游戏管理器初始化完成");
}
}
// 调用示例
class Program
{
static void Main(string[] args)
{
// 直接通过Instance获取单例实例
GameManager.Instance.InitGame();
// 验证单例特性,两次获取的实例是同一个
var instance1 = GameManager.Instance;
var instance2 = GameManager.Instance;
Console.WriteLine(instance1 == instance2); // 输出True
}
}
进阶优化点
支持带参数的构造函数
上面的基础版本要求T必须有公共无参构造函数,如果单例类需要带参数的初始化逻辑,我们可以调整基类的实现,通过委托传入实例创建方法。
using System;
public abstract class SingletonBase<T> where T : class
{
private static T _instance;
private static readonly object _lockObj = new object();
// 存储实例创建的委托
private static Func<T> _createInstanceFunc;
protected SingletonBase() { }
// 初始化单例创建方法的静态方法,需要在第一次获取实例前调用
public static void SetCreateInstanceFunc(Func<T> createFunc)
{
_createInstanceFunc = createFunc;
}
public static T Instance
{
get
{
if (_instance == null)
{
lock (_lockObj)
{
if (_instance == null)
{
if (_createInstanceFunc == null)
{
throw new InvalidOperationException("未设置单例实例创建方法");
}
_instance = _createInstanceFunc();
}
}
}
return _instance;
}
}
}
// 带参数构造函数的单例类示例
public class ConfigManager : SingletonBase<ConfigManager>
{
private string _configPath;
// 私有构造函数,参数通过创建委托传入
private ConfigManager(string configPath)
{
_configPath = configPath;
}
public string GetConfigPath()
{
return _configPath;
}
}
// 调用示例
class Program
{
static void Main(string[] args)
{
// 先设置实例创建委托,传入构造参数
ConfigManager.SetCreateInstanceFunc(() => new ConfigManager("config/app.json"));
// 获取单例实例
var configPath = ConfigManager.Instance.GetConfigPath();
Console.WriteLine(configPath); // 输出config/app.json
}
}
避免单例被继承滥用
如果希望泛型单例基类只能被指定的类继承,或者避免用户随意继承基类,我们可以把基类的构造函数设置为private,同时结合嵌套类的方式实现更严格的约束,不过这种方式会稍微增加代码复杂度,实际开发中可以根据项目需求选择是否添加。
注意事项
- 泛型单例基类中的静态实例是和泛型参数T绑定的,不同的T类型会对应不同的静态实例,不会互相干扰。
- 如果单例类需要在程序退出时释放资源,可以在基类中添加
Dispose方法,手动释放实例引用。 - 不要在单例类的构造函数中做太多耗时的初始化操作,避免第一次获取实例时阻塞调用线程。
- 如果项目使用依赖注入框架,建议优先使用框架提供的单例注册方式,泛型单例基类更适合没有引入重量级框架的小型项目。