Как абстрагироваться от одноэлементного класса?
Вот как я пишу свои одноэлементные классы.
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
.
- Он должен иметь локальное свойство только для чтения, чтобы получить экземпляр.
Проблема
Я использую этот шаблон для нескольких классов и всегда должен писать один и тот же код. Как я могу написать что-то, что используется повторно, когда мне нужен синглтон?
Ответы
Ответ 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();