[C#]单例模式实践

什么是单例模式

  • 单例,可以向其他对象提供一个全局唯一的对象,比较类似C的全局变量
  • 也就是说无论在哪,都能访问到相同的对象
  • 比如说Unity的游戏状态,可以由一个单例类管理,为了全局访问当前状态

实现

简单单例

  • 最简单的单例类例子:
1
2
3
4
5
6
public class Singleton
{
public static Singleton Instance { get; } = new Singleton();

private Singleton() { }
}

饿汉式

俗称饿汉式单例,由CLR保证在类初始化时就构造完毕,没有线程安全问题。但是有没有办法让它在我们第一次使用时才加载呢。

  • 最简单的懒惰单例
C#8
1
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异常

  • Lazy真好用。但我还想要一劳永逸得写单例
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完事了


[C#]单例模式实践
https://ksgfk.github.io/2020/06/23/CSharp-单例模式实践/
作者
ksgfk
发布于
2020年6月23日
许可协议