如何抽象单例类?

Rea*_*lar 30 c# singleton design-patterns

这就是我编写单例类的方法.

public class MyClass
{
    /// <summary>
    /// Singleton
    /// </summary>
    private static MyClass instance;

    /// <summary>
    /// Singleton access.
    /// </summary>
    public static MyClass Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new MyClass();
            }
            return _instance;
        }
    }

    private MyClass() { .... }
}
Run Code Online (Sandbox Code Playgroud)

如何创建可重用的单例模式?

单身模式存在以下挑战.

  • 构造函数是privateprotected.
  • 基类无法实例化继承的类.所以你可以重用一个共同的摘要MyAbstractSingletonClass.
  • 它必须具有本地只读属性才能获取实例.

问题

我在许多类上使用这种模式,并且总是必须编写相同的代码.我怎么能写出一些我需要单身时可以重复使用的东西?

BTo*_*TKD 51

您可以结合使用自引用泛型类型约束和" new() "类型约束来实现此目的.

"new"约束确保任何子类始终具有无参数构造函数,因此_instance = new T();始终有效.

自引用类型约束确保"实例"静态属性始终返回正确的类型; 不是"基础"类型.您的单例基类看起来像这样:

public abstract class SingletonBase<T> 
    where T : SingletonBase<T>, new()
{
    private static T _instance = new T();
    public static T Instance
    {
        get
        {                
            return _instance;
        }   
    }
}
Run Code Online (Sandbox Code Playgroud)

您的子课程将如下所示:

public class MyChildSingleton : SingletonBase<MyChildSingleton>
{
    //Done!
}
Run Code Online (Sandbox Code Playgroud)

当然,如果你希望你的单例是通用的,你也应该稍微更改你的"创建单例实例"代码,使用" 双重检查锁 "模式或Lazy类,使其成为线程安全的.

最重要的警告:如果使用此方法,"new()"约束几乎可以确保您的类始终具有公共的无参数构造函数.这意味着你的最终用户可以随时调用,new MyChildSingleton()如果他们真的想要,完全绕过你的单例实例.你的单身人士将"按惯例",而不是严格执行.为了解决这个问题需要更多的工程.在上面的场景中,约定似乎是您应该将静态实例命名为" Default"而不是" Instance.".这巧妙地传达了这样一个事实:你的类提供了一个"建议的"单例实例,但使用它在技术上是可选的.

我已经尝试严格执行单例模式,最终结果是使用反射来手动调用私有构造函数.您可以在此处查看我的完整代码尝试.

  • @MathewFoscarini - 请在使用此代码之前查看Jon Skeet关于Singletons的文章:http://csharpindepth.com/Articles/General/Singleton.aspx因为它可能需要一些多线程触摸并且可以从"懒惰"类中受益. (6认同)
  • 这些类型不是单身人士.您必须在这些类型上有一个公共构造函数来满足该泛型约束,因此任何人都可以创建它们的新实例,使它们不是单例. (2认同)

Buv*_*uvy 8

真正的解决方案是从BTownTKD的方法开始,但使用Activator.CreateInstance方法扩充它,它允许您的子类保留私有构造函数.

家长班

public abstract class BaseSingleton<T> where T :
    BaseSingleton<T>
{
    private static readonly ThreadLocal<T> Lazy =
        new ThreadLocal<T>(() =>
            Activator.CreateInstance(typeof(T), true) as T);

    public static T Instance => Lazy.Value;
}
Run Code Online (Sandbox Code Playgroud)

儿童班

public sealed class MyChildSingleton : BaseSingleton<MyChildSingleton>
{
    private MyChildSingleton() { }
}
Run Code Online (Sandbox Code Playgroud)

完整的实施示例

  • 我不确定为什么这不是公认的答案或为什么没有收到任何赞成票.我能想到的解决方案唯一需要注意的是,私有无参数构造函数的存在只能在运行时而不是在编译时强制执行.除此之外,这是迄今为止列出的最强大的解决方案. (2认同)

Eug*_*rin 5

添加到BTownTKD的答案,在运行时限制构造函数调用实际上非常简单(在编译时不确定).您所做的就是在SingletonBase中添加一个受保护的构造函数,如果_instance不为null,则抛出异常.即使构造函数是从外部调用的第一个东西,也会抛出异常.

我设法在单独的基础上应用这个技术,并且如下所述使它变得懒惰和线程安全:http://csharpindepth.com/Articles/General/Singleton.aspx

结果(使用说明):

/// <summary>
/// Generic singleton class, providing the Instance property, and preventing manual construction.
/// Designed as a base for inheritance trees of lazy, thread-safe, singleton classes.
/// Usage:
/// 1. Sub-class must use itself, or its sub-class, as the type parameter S.
/// 2. Sub-class must have a public default constructor (or no constructors).
/// 3. Sub-class might be abstract, which requires it to be generic and demand the generic type
///    have a default constructor. Its sub-classes must answer all these requirements as well.
/// 4. The instance is accessed by the Instance getter. Using a constructor causes an exception.
/// 5. Accessing the Instance property in an inner initialization in a sub-class constructor
///    might cause an exception is some environments.
/// </summary>
/// <typeparam name="S">Lowest sub-class type.</typeparam>
public abstract class Singleton<S> where S : Singleton<S>, new()
{
    private static bool IsInstanceCreated = false;
    private static readonly Lazy<S> LazyInstance = new Lazy<S>(() =>
        {
            S instance = new S();
            IsInstanceCreated = true;
            return instance;
        });

    protected Singleton()
    {
        if (IsInstanceCreated)
        {
            throw new InvalidOperationException("Constructing a " + typeof(S).Name +
                " manually is not allowed, use the Instance property.");
        }
    }

    public static S Instance
    {
        get
        {
            return LazyInstance.Value;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我必须说我没有进行密集的多线程测试,但正如一些人已经说过的那样,你总是可以使用旧的双重检查技巧.