泛型单例必须用Lazy实现线程安全延迟初始化,要求T为引用类型且有无参构造函数;支持私有构造时需反射创建;Unity中需结合MonoBehaviour与DontDestroyOnLoad;生产环境推荐DI容器注册。

泛型单例不能靠“写一次、到处复制”的传统方式实现,必须兼顾线程安全、类型约束和编译正确性。核心是:用 Lazy
基础写法(推荐,线程安全)
这是最简、最可靠的基础版本:
- 要求 T 是引用类型且有无参 public 构造函数
- 利用 Lazy
内置的线程安全机制,无需 lock 或双重检查 - 每个闭合泛型类型(如 Singleton
和 Singleton )各自独立,互不干扰
public class Singleton
{
private static readonly Lazy
public static T Instance => _lazy.Value;
}
支持私有构造函数的变体
如果目标类只提供 private 构造函数(如某些工具类、密封类),需借助反射创建实例:
- 去掉
new()约束,改用where T : class - 在 Lazy 初始化委托中用
GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance)获取私有构造器 - 注意:反射调用性能略低,且需确保目标类型确实只有一个无参构造函数
public class Singleton
{
private static readonly Lazy
{
var ctor = typeof(T).GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null, Type.EmptyTypes, null);
if (ctor == null) throw new InvalidOperationException($"Type {typeof(T)} has no parameterless constructor.");
return (T)ctor.Invoke(null);
});
public static T Instance => _lazy.Value;
}
Unity 中继承 MonoBehaviour 的泛型单例
游戏开发中,单例常需挂载到 GameObject 并跨场景存活,此时不能只靠 new T():
- 必须继承
MonoBehaviour,用FindObjectOfType查找已有实例() - 未找到时动态创建 GameObject 并 AddComponent,再调用
DontDestroyOnLoad - 加入
bAppQuitting标志防止 OnDestroy 后误取已销毁实例 - 仍需用 lock + double-check 保证多线程下创建安全(尤其在 Awake 早期)
生产环境更推荐 DI 容器注册
手写泛型单例在复杂依赖、生命周期管理、单元测试隔离上容易出问题:
- ASP.NET Core 中注册开放泛型:
services.AddSingleton(typeof(IService), typeof(Service)); - DI 自动处理线程安全、构造注入、Dispose 传播,比手动 Lazy 更健壮
- 避免静态状态导致的测试污染——每次测试可新建 ServiceCollection 实例











