C# 中单例模式的设计缺陷






2.92/5 (16投票s)
如何为 .NET 创建泛型单例。
范例
考虑这种常见的单例模式实现
public class Singleton
{
static private readonly object locker = new object();
static public Singleton Instance
{
get
{
lock ( locker )
{
if ( _Instance == null ) _Instance = new Singleton();
return _Instance;
}
}
}
static private volatile Singleton _Instance;
private Singleton()
{
}
}
问题在于,如果没有private
构造函数,你可以继承这个类并创建一个public
构造函数。 此外,允许使用static
成员。 这已经不是一个单例了。 将类设置为sealed
可能是一个可以接受的解决方案,但你必须逐个实现单例,即超过十行代码。 因此,编写这样的单例可能会导致许多错误和困难。 因子分解的思考方式不仅是一个敏捷原则,它还是一个数学定理。
定义泛型单例
一个通用的解决方案是检查是否缺少static
成员,并且只有一个无参数的private
构造函数,否则将抛出异常。 继承此类的单例不能被继承,并且必须是sealed
。 此外,单例类型的实现会在程序启动时进行检查。 因此,它不是最好的解决方案,但唯一需要做的就是创建一个无参数的private
构造函数,没有static
成员,并密封该类。
以下是建议的单例的成员
abstract public class Singleton<T> where T : Singleton<T>;
这是泛型abstract
类的声明,其中T
是单例。 通过这样写,类型一致性就很清楚了。
static public string Filename;
static public void Save();
这用于为持久单例提供磁盘存储并保存其状态。
static public T Instance;
static public T GetInstance();
这是访问单例实例的经典方式。
static public T GetPersistentInstance(string filename);
static public T GetPersistentInstance();
这将创建一个持久实例:它从磁盘反序列化对象或创建一个新对象。 它使用特定的文件名或系统名称。 注意:在使用单例后定义名称不会加载新实例,如果存在本地化,则应抛出错误。
static private T CreateInstance();
static internal ConstructorInfo CheckImplementation();
这通过调用默认的private
构造函数创建实例。 如上所示,将检查单例实现的有效性。
以下是Serialize
和Deserialize
函数
static public void Serialize(this object obj, string filename, int buffersize)
{
if ( !obj.GetType().IsSerializable )
throw new IOException(SystemManager.Language.Get("ObjectIsNotSerializable",
obj.GetType().Name));
using ( FileStream f = new FileStream(filename,
FileMode.Create, FileAccess.Write,
FileShare.None,
buffersize
) )
new BinaryFormatter().Serialize(f, obj);
}
static public object Deserialize(this string filename, int buffersize)
{
using ( FileStream f = new FileStream(filename,
FileMode.Open, FileAccess.Read,
FileShare.None,
buffersize) )
return new BinaryFormatter().Deserialize(f);
}
编写单例
namespace Ordisoftware.Core.ObjectModel
{
[Serializable]
abstract public class Singleton<T> where T : Singleton<T>
{
static private readonly object locker = new object();
static protected void DoError(string s)
{
throw new SingletonException(SystemManager.Language.Get(s),
typeof(T));
}
static public string Filename
{
get { return _Filename; }
set
{
if ( _Filename == value ) return;
lock ( locker )
{
if ( FileTool.Exists(_Filename) ) FileTool.Move(_Filename, value);
_Filename = value;
}
}
}
static private volatile string _Filename;
static public void Save()
{
lock ( locker )
if ( !( _Filename.IsNullOrEmpty() && Instance.IsNull() ) )
{
FolderTool.Check(_Filename);
Instance.Serialize(_Filename);
}
}
~Singleton()
{
try { Save(); }
catch (Exception e) { ShowError(e.Message); }
}
static public T Instance
{
get
{
lock ( locker )
{
if ( _Instance == null )
if ( FileTool.Exists(_Filename) )
_Instance = (T)_Filename.Deserialize();
else _Instance = CreateInstance();
return _Instance;
}
}
}
static private volatile T _Instance;
static public T GetInstance()
{
return Instance;
}
static public T GetPersistentInstance(string filename)
{
Filename = filename;
return Instance;
}
static public T GetPersistentInstance()
{
if ( _Instance != null ) return _Instance;
Type type = typeof(T);
string s = type.Namespace + '.' + type.Name.Replace('`', '_');
foreach ( Type t in type.GetGenericArguments() )
s += " " + t.FullName;
s = SystemManager.FolderSystem + s + SystemManager.ExtObjectFile;
return GetPersistentInstance(s);
}
static private T CreateInstance()
{
return (T)CheckImplementation().Invoke(null);
}
static internal ConstructorInfo CheckImplementation()
{
Type type = typeof(T);
if ( !type.IsSealed ) DoError("SingletonMustBeSealed");
var bf1 = BindingFlags.Static | BindingFlags.NonPublic |
BindingFlags.Public;
var bf2 = BindingFlags.Instance | BindingFlags.Public;
var bf3 = BindingFlags.Instance | BindingFlags.NonPublic;
if ( type.GetMembers(bf1).Length != 0 )
DoError("SingletonNoStaticMembers");
if ( type.GetConstructors(bf2).Length != 0 )
DoError("SingletonNoPublicConstructors");
ConstructorInfo[] list = type.GetConstructors(bf3);
if ( ( list.Length != 1 ) || ( list[0].GetParameters().Length != 0 )
|| ( !list[0].IsPrivate ) )
DoError("SingletonOnlyOnePrivateConstructor");
return l[0];
}
}
}
启动检查
namespace Ordisoftware.Core
{
static public class SystemManager
{
static public void Initialize()
{
Type type = Type.GetType("Ordisoftware.Core.ObjectModel.Singleton`1");
if ( type != null )
{
MethodInfo method;
string name = "CheckImplementation";
var bf = BindingFlags.InvokeMethod | BindingFlags.FlattenHierarchy
| BindingFlags.Static | BindingFlags.NonPublic;
var list = ObjectUtility.GetClasses(t => ( t.BaseType.Name == type.Name )
&& ( t.BaseType.Namespace == type.Namespace ));
foreach ( var t in list )
try
{
if ( !t.ContainsGenericParameters ) method = t.GetMethod(name, bf);
else
{
Type[] p = t.GetGenericArguments();
for ( int i = 0; i < p.Length; i++ ) p[i] = typeof(object);
method = t.MakeGenericType(p).GetMethod(name, bf);
}
method.Invoke(null, new object[0]);
}
catch ( Exception e ) { b = true; ShowException(e); }
}
}
}
}
以下是GetClasses
函数
static public TypeList GetClasses(Func<Type, bool> select)
{
return GetList(t => t.IsClass, select);
}
static private TypeList GetList(Func<Type, bool> check,
Func<Type, bool> select)
{
TypeList list = new TypeList();
Type[] l1 = Assembly.GetExecutingAssembly().GetTypes();
if ( select == null ) list.AddRange(l1);
else
foreach ( Type t in l1 )
if ( check(t) && select(t) ) list.Add(t);
Module[] l2 = Assembly.GetEntryAssembly().GetLoadedModules();
if ( select == null ) list.AddRange(l1);
else
foreach ( Module m in l2 )
foreach ( Type t in m.Assembly.GetTypes() )
if ( check(t) && select(t) ) list.Add(t);
list.Sort((v1, v2) => v1.FullName.CompareTo(v2.FullName));
return list;
}
使用示例
每次执行都会将此程序显示的值增加 10
[Serializable]
public class MySingleton : Singleton<MySingleton>
{
public int Value { get; set; }
private MySingleton() { }
}
static class Program
{
[STAThread]
static void Main(string[] args)
{
SystemManager.Initialize();
try
{
var v = MySingleton.GetPersistentInstance();
v.Value += 10;
Console.WriteLine("MySingleton.Value = " +
MySingleton.Instance.Value);
}
catch ( Exception e ) { Debugger.ManageException(null, e); }
finally { SystemManager.Finalize(); }
}
}
缺少的“单例”语言关键字
在 C# 中实现单例的最佳方法是创建一个static
类,但这可能会导致序列化以及对象初始化时间的问题,无论是否考虑惰性。
理想的情况是拥有一个像单例这样的语言关键字:一个没有static
字段且只有一个没有参数且没有访问修饰符的构造函数。 只有标记为abstract
才能被继承。 它可以像static
类一样使用,但其行为类似于实例化的类。 它可以是可序列化和可释放的:首次使用时,如果关联了流,则反序列化对象,或者创建一个新的单例实例,释放时,序列化单例;如果没有关联流,则不执行任何操作;更改流会将实例从旧流移动到新流;如果在已实例化的单例上设置流,则如果新流本地化了现有项,则会导致使用异常。
[Serializable]
[SingletonPersistence(false)] // don't use a default system stream
public singleton MySingleton
{
public int Value {get; set; }
MySingleton()
{
// Code executed on first access
}
}
var stream1 = new SingletonFileStream("c:\\mysingleton.bin");
var stream2 = new SingletonSystemStream();
MySingleton.SetStream(stream1);
MySingleton.Value += 10;
MySingleton.SetStream(stream2);
MySingleton.Value += 10;
MySingleton.SaveState();
推荐文章
历史
本文写于 2009 年 7 月 21 日,基于为 Ordisoftware Core Library for .NET 项目开发的愿景.
源代码和演示于 2012 年 6 月 26 日更新,