Подтвердить что ты не робот

Как абстрагироваться от одноэлементного класса?

Вот как я пишу свои одноэлементные классы.

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() { .... }
}

Как создать шаблон Singleton, который можно использовать повторно?

Модели Singleton представляют следующие проблемы.

  • Конструктор private или protected.
  • Базовый класс не может создать экземпляр унаследованного класса. Поэтому вы можете повторно использовать общий абстрактный MyAbstractSingletonClass.
  • Он должен иметь локальное свойство только для чтения, чтобы получить экземпляр.

Проблема

Я использую этот шаблон для нескольких классов и всегда должен писать один и тот же код. Как я могу написать что-то, что используется повторно, когда мне нужен синглтон?

4b9b3361

Ответ 1

Вы можете достичь этого, используя комбинацию саморегуляции общего ограничения типа и " new()" ограничение типа.

"Новое" ограничение гарантирует, что любой дочерний класс всегда будет иметь конструктор без параметров, поэтому _instance = new T(); всегда будет работать.

Ограничение типа саморегуляции гарантирует, что статическое свойство "Instance" всегда возвращает правильный тип; а не "базовый" тип. Ваш базовый класс singleton будет выглядеть примерно так:

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

Ваши дочерние классы будут выглядеть следующим образом:

public class MyChildSingleton : SingletonBase<MyChildSingleton>
{
    //Done!
}

Конечно, если вы хотите, чтобы ваш singleton был универсальным, вы также должны немного изменить свой код "create singleton", чтобы использовать " double -check lock", или Lazy класс, чтобы сделать его потокобезопасным.

Большое оговорка. Если вы используете этот метод, ограничение "new()" в значительной степени гарантирует, что ваш класс всегда будет иметь открытый конструктор без параметров. Это означает, что ваши конечные пользователи всегда могут просто вызвать new MyChildSingleton(), если они действительно захотят, полностью обходя ваш экземпляр singleton. Ваш синглтон будет "по соглашению" вместо строгого соблюдения. Чтобы обойти это, потребуется немного больше техники. В приведенном выше сценарии соглашение, похоже, означает, что вы должны называть свой статический экземпляр "Default" вместо "Instance". Это тонко передает тот факт, что ваш класс предлагает "предложенный" экземпляр singleton, но использование его технически необязательно.

Я предпринял некоторые попытки строго соблюдать шаблон singleton, и конечным результатом было использование отражения для ручного вызова частного конструктора. Вы можете увидеть мою полную попытку кода здесь.

Ответ 2

Вы правы, поскольку в настоящее время вы не можете этого достичь. Обратите внимание, что при таком подходе вы получите один экземпляр singleton для каждого уникального производного типа:

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = new MyDerivedClass();
            Console.WriteLine(x.ToString());
            Console.WriteLine(x.Instance.ToString());

            Console.ReadKey();
        }
    }


    public abstract class MyBaseClass<T> where T : class, new()
    {
        protected T GetInstance()
        {
            if (_instance == null)
            {
                lock (_lockObj)
                {
                    if (_instance == null)
                        _instance = new T();
                }
            }
            return _instance;
        }

        public T Instance
        {
            get { return GetInstance(); }
        }

        private volatile static T _instance;
        private object _lockObj = new object();
    }

    public class MyDerivedClass : MyBaseClass<MyDerivedClass>
    {
        public MyDerivedClass() { }
    }

}

Ответ 3

Добавляя к ответу BTownTKD, на самом деле довольно просто ограничить вызов конструктора во время выполнения (что не представляется возможным при компиляции). Все, что вы делаете, это добавить защищенный конструктор в SingletonBase, который выдает исключение, если _instance не является нулевым. Исключение будет выбрано, даже если конструктор является первым, что нужно вызвать извне.

Мне удалось применить эту технику в одноэлементной базе, а также сделать ее ленивой и потокобезопасной, как описано здесь: 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;
        }
    }
}

Я должен сказать, что я не проводил интенсивное многопоточное тестирование, но, как уже говорили некоторые, вы всегда можете использовать старый трюк с двойной проверкой.

Ответ 4

Простой ответ заключается в том, что вы не можете реализовать шаблон singleton в базовом классе.

Тем не менее, вы можете реализовать другие шаблоны дизайна для создания, которые могут быть подходящими для того, что вы пытаетесь выполнить. Например, посмотрите Аннотация Factory.

Ответ 5

Истинное решение начинается с подхода 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;
}

Детский класс

public sealed class MyChildSingleton : BaseSingleton<MyChildSingleton>
{
    private MyChildSingleton() { }
}

Пример полной реализации

Ответ 6

Недавно я предложил этот ответ на соответствующий вопрос:

fooobar.com/questions/237758/...

С помощью этого метода базовый класс управляет созданием всех экземпляров производных классов, поскольку все конструкторы производных классов требуют объекта, который может предоставить только базовый класс, и нет необходимости в запрещении конструктора без параметров.

Ответ 7

Общая, оптимизированная реализация поточно-безопасного запрещенного шаблона:

public abstract class Singleton<T> where T : class, new() {
    public static readonly T Instance = new T();
}

Статический конструктор исключается в пользу производительности, так как лень не требуется, а свойство заменяется открытым полем для краткости

Реализация

public sealed class Foo : Singleton<Foo> {
    public void Bar() {
        //...
    }
}

Использование

Foo.Instance.Bar();