65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.92/5 (16投票s)

2009 年 7 月 27 日

LGPL3

3分钟阅读

viewsIcon

55321

downloadIcon

313

如何为 .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构造函数创建实例。 如上所示,将检查单例实现的有效性。

以下是SerializeDeserialize函数

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 日更新,

© . All rights reserved.