C# 中使用反射的通用单例模式






4.67/5 (44投票s)
使用泛型类创建所有单例。
引言
本文介绍了一个通用类,该类使用反射来创建任何类的单个实例。它可以用于从具有非公共构造函数的公共类实例化单例,以及从非公共类实例化单例。
此解决方案的主要动机是将单例模式只编写在一个地方,以便所有需要遵循该模式的类都可以重用。
只需使用 Singleton<T>.Instance
即可获得单例实例。
背景
已经有一些非常好的文章(Lazy vs. Eager Init Singletons / Double-Check Lock Pattern 和 Implementing the Singleton Pattern in C#)描述了在 .NET 中开发单例的挑战和注意事项;我不会再次详细介绍。这里和那里也有一些使用泛型的单例(例如,请参见Generic Singleton Provider),但是没有一个能够解决创建具有不可访问构造函数的类的单个实例的问题(这是所有单例都应该具有的)。
设计
Singleton<T>
使用 双重检查锁定模式,而不是静态类型初始化。选择这种方法的原因是在 .NET 中,类型初始化器失败后的错误恢复和重试是不可能的(一旦类型初始化器失败,尝试创建类实例将始终抛出异常,即使错误已被同时解决)。所选的实现策略为更好的错误恢复提供了更大的灵活性。
Singleton<T>
还强制目标类构造函数为 private
或 protected
,以防止随意的类实例化。
Singleton<T>
这是通用 Singleton
类
public static class Singleton<T>
where T : class
{
static volatile T _instance;
static object _lock = new object();
static Singleton()
{
}
public static T Instance
{
get
{
if (_instance == null)
lock (_lock)
{
if (_instance == null)
{
ConstructorInfo constructor = null;
try
{
// Binding flags exclude public constructors.
constructor = typeof(T).GetConstructor(BindingFlags.Instance |
BindingFlags.NonPublic, null, new Type[0], null);
}
catch (Exception exception)
{
throw new SingletonException(exception);
}
if (constructor == null || constructor.IsAssembly)
// Also exclude internal constructors.
throw new SingletonException(string.Format("A private or " +
"protected constructor is missing for '{0}'.", typeof(T).Name));
_instance = (T)constructor.Invoke(null);
}
}
return _instance;
}
}
}
类型参数 T
表示必须像单例一样实例化的类。
Singleton<T>
使用 class
约束来强制类型参数为引用类型。此处未使用 new()
约束(它强制类型参数具有公共的无参数构造函数),因为泛型类的目标是为具有非公共构造函数的类提供单实例创建。
_instance
使用 volatile
关键字定义,并且使用虚拟对象 _lock
来使用双重检查锁定模式同步线程。 有关此策略的详细信息,请参见 Lazy vs. Eager Init Singletons / Double-Check Lock Pattern 。
Singleton<T>
具有静态构造函数:它仅确保在首次调用 Instance
属性时(而不是之前)发生静态字段初始化。
_instance
字段的初始化是通过使用类型参数的反射来实现的。 typeof(T)
首先获取类型参数的类型,然后 GetConstructor
获取其构造函数,最后 Invoke
返回新实例。 BindingFlags
告诉 GetConstructor
在类型的非公共(BindingFlags.NonPublic
)实例成员(BindingFlags.Instance
)中搜索构造函数。搜索不包括 public
成员,并且最后一个 if
语句排除 internal
构造函数,因为保持单例构造函数 private
或 protected
是最佳实践(您不希望其他人实例化这些类)。
您可以推断,如果类型参数不是类,GetConstructor
可能会引发异常,因为 class
约束也允许接口、委托或数组类型。 使用这些约束,您能做的就这么多了。
使用 Singleton<T>
单例模式的另一个最佳实践是让每个单例返回其自己的单个实例。 这通常通过名为 Instance
的静态属性来完成。 以下代码段显示了如何在属性中使用 Singleton<T>
。
public static SingleInstanceClass Instance
{
get
{
return Singleton<SingleInstanceClass>.Instance;
}
}
演示
演示显示了 SingleInstanceClass
单例的生命周期。 该代码是不言自明的
// SingleInstanceClass is a Singleton.
public class SingleInstanceClass
{
// Private constructor to prevent
// casual instantiation of this class.
private SingleInstanceClass()
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("SingleInstanceClass created.");
Console.ResetColor();
_count++;
}
// Gets the single instance of SingleInstanceClass.
public static SingleInstanceClass Instance
{
get { return Singleton<SingleInstanceClass>.Instance; }
}
public static int Count { get { return _count; } }
public static void DoStatic()
{ Console.WriteLine("DoStatic() called."); }
public void DoInstance()
{ Console.WriteLine("DoInstance() called."); }
// Instance counter.
private static int _count = 0;
}
class Program
{
static void Main()
{
// Show that initially the count is 0; calls
// a static property which doesn't create the Singleton.
Console.WriteLine("---");
Console.WriteLine("Initial instance count: {0}",
SingleInstanceClass.Count);
// Similar to above; show explicitly that calling
// a static methods doesn't create the Singleton.
Console.WriteLine("---");
Console.WriteLine("Calling DoStatic()");
SingleInstanceClass.DoStatic();
Console.WriteLine("Instance count after DoStatic(): {0}",
SingleInstanceClass.Count);
// Show that getting the instance creates the Singleton.
Console.WriteLine("---");
Console.WriteLine("Calling DoInstance()");
SingleInstanceClass.Instance.DoInstance();
Console.WriteLine("Instance count after first DoInstance(): {0}",
SingleInstanceClass.Count);
// Show that getting the instance
// again doesn't re-instantiate the class.
Console.WriteLine("---");
Console.WriteLine("Calling DoInstance()");
SingleInstanceClass.Instance.DoInstance();
Console.WriteLine("Instance count after second DoInstance(): {0}",
SingleInstanceClass.Count);
Console.ReadKey();
}
}
程序输出为
结论
在 C# 中有很多种实现单例模式的方法。 Singleton<T>
使用反射使其成为一个一次编写,全部解决方案。
历史
- 2009-04-18:重构为使用双重检查锁定模式和
volatile
关键字,而不是类型初始化器。 另外添加了SingletonException
。 - 2009-06-07:将
typeof(T).InvokeMember
替换为typeof(T).GetConstructor
以支持 Compact Framework。