什么是单例模式
- 单例,可以向其他对象提供一个全局唯一的对象,比较类似C的全局变量
- 也就是说无论在哪,都能访问到相同的对象
- 比如说Unity的游戏状态,可以由一个单例类管理,为了全局访问当前状态
实现
简单单例
1 2 3 4 5 6
| public class Singleton { public static Singleton Instance { get; } = new Singleton();
private Singleton() { } }
|
饿汉式
俗称饿汉式单例,由CLR保证在类初始化时就构造完毕,没有线程安全问题。但是有没有办法让它在我们第一次使用时才加载呢。
C#81 2 3 4 5 6 7 8
| public class Singleton { private static Singleton _instance;
public static Singleton Instance => _instance ??= new Singleton();
private Singleton() { } }
|
System.Lazy
明眼人一眼就能看出,多个线程同时访问Instance属性可能会导致实例化多个对象,这不符合单例的定义!当然保证只有一个线程访问的话一点毛病都没有说你呢Unity。
- 可能是最佳的单例写法,使用
System.Lazy<T>
.Net4之后能用(还有人用4之前的Framework?Core都出了多少年了不会吧不会吧1 2 3 4 5 6 7 8
| public class Singleton { private static readonly Lazy<Singleton> Lazy = new Lazy<Singleton>(() => new Singleton(), true);
public static Singleton Instance => Lazy.Value;
private Singleton() { } }
|
官方文档 https://docs.microsoft.com/zh-cn/dotnet/api/system.lazy-1?view=netstandard-2.0
单例模板
需要注意的是,由于我们单例的构造函数必须是私有的,而Lazy的Lazy<T>()
和Lazy<T>(Boolean)
构造函数只能调用T类型的公共且零参的构造函数来实例化对象(也就是说构造函数必须是公共的)这话说得怎么这么别扭,所以我们只能调用Lazy<T>(Func<T>, Boolean)
来构造Lazy对象。否则会抛System.MissingMemberException
异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| using System; using System.Linq; using System.Reflection;
public abstract class Singleton<T> where T : Singleton<T> { private static Lazy<T> _lazy = new Lazy<T>(() => { var type = typeof(T); if (type.IsAbstract) { throw new ArgumentException($"单例类{type.FullName}不能是抽象的"); }
var constructors = type.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (constructors.Length != 1) { throw new ArgumentException($"单例类{type.FullName}不能有多个构造方法"); }
var constructor = constructors.SingleOrDefault(c => !c.GetParameters().Any() && c.IsPrivate); if (constructor == null) { throw new ArgumentException($"单例类{type.FullName}的构造方法必须是私有且无参的"); }
return (T) Activator.CreateInstance(type, true); }, true);
public static T Instance => _lazy.Value; }
|
通过反射检查类和构造方法是否符合要求,只需继承Singleton<T>
就可以了
1 2 3 4
| public class Manager : Singleton<Manager> { private Manager() { } }
|
简单,有效,可靠(大概,反正我没遇到坑)
1 2 3 4
| private void Update() { var gm = Manager.Instance; }
|
调用Instance完事了