Общий абстрактный базовый класс для синглетонов (C#) - невозможно создать экземпляр частного экземпляра Lazy
В настоящее время у меня есть коллекция из 6 или 7 синглетонов, которые делают почти одно и то же (см. For
метод в приведенном ниже примере), но с другим внутренним запросом к БД и возвращает коллекцию разных объектов (поэтому анализ результатов БД различен в каждом синглтоне).
Поэтому, используя этот вопрос в качестве своей базы, я пытался построить абстрактный базовый класс в C# для этих синглетонов.
Есть похожие вопросы по SO, но никто не реализует Lazy
, который я хочу.
Пока у меня есть это
public abstract class SingletonBase<T> where T : class, new()
{
private static Lazy<SingletonBase<T>> _lazy;
private static readonly object _lock = new object();
public static SingletonBase<T> Instance
{
get
{
if (_lazy != null && _lazy.IsValueCreated)
{
return _lazy.Value;
}
lock (_lock)
{
if (_lazy != null && _lazy.IsValueCreated)
{
return _lazy.Value;
}
***** this is the problem line *****
_lazy = new Lazy<SingletonBase<T>>(new T());
}
return _lazy.Value;
}
}
public abstract IEnumerable<T> For(string systemLangCode);
}
Тем не менее, проблема возникает на линии
_lazy = new Lazy<SingletonBase<T>>(new T());
Visual Studio говорит мне: "Не удается разрешить конструктор" Ленивый
Я не уверен, что должно быть передано в конструктор для Lazy<SingletonBase<T>>
или я не в том направлении?
2 ответа
Несмотря на несколько полезных советов, я все же хотел использовать одноэлементное решение для обеспечения решения, потому что я чувствовал, что смогу сделать это в меньшем количестве и проще кода. И вот что я сделал.
Вот базовый базовый класс.
public abstract class SingletonBase<S, T> where S : class, new()
{
protected static readonly Dictionary<string, List<T>> Dictionary = new Dictionary<string, List<T>>();
private static readonly S _instance = new S();
private static readonly object _lock = new object();
public static S Instance
{
get
{
lock (_lock)
{
return _instance;
}
}
}
public IEnumerable<T> For(string systemLangCode)
{
systemLangCode = systemLangCode.ToLower();
if (!Dictionary.ContainsKey(systemLangCode))
{
PopulateDictionary(systemLangCode);
}
return Dictionary.FirstOrDefault(d => d.Key == systemLangCode).Value;
}
protected abstract void PopulateDictionary(string systemLangCode);
}
Что я на самом деле здесь делаю, кроме создания и кэширования экземпляра производного типа, S
, обрабатывает запросы на коллекцию T
, Вместо того, чтобы запрашивать БД и хранить все возможные наборы записей, я запрашиваю набор записей при необходимости, используя systemLangCode
в качестве переменной в запросе, проанализируйте этот набор записей в List<T>
, а затем сохранить эту коллекцию в Dictionary
с помощью systemLangCode
как ключ.
Следовательно, запрос выполняется только в том случае, если этот набор результатов ранее не был получен этим синглтоном.
А вот и вывод вышеприведенного базового класса.
public sealed class Languages : SingletonBase<Languages, Language>
{
protected override void PopulateDictionary(string systemLangCode)
{
var languages = new List<Language>();
// some DB stuff
Dictionary.Add(systemLangCode, languages);
}
}
Этот базовый класс теперь позволяет мне создавать серию производных классов, которые содержат немного больше, чем запрос к базе данных. Таким образом, теперь у меня есть одно определение синглтона, которое я могу использовать для хранения, например, набора языков на разных языках, набора стран на разных языках, набора часовых поясов на разных языках и т. Д.
Пример использования:
var langsDe = Languages.Instance.For("de");
var langsEn = Languages.Instance.For("en");
И если я позвоню позже
var langsDe = Languages.Instance.For("de");
Тогда это будет извлечено из внутреннего Dictionary
,
или я не в том направлении?
Похоже на это.
Прежде всего, _lazy
поле имеет тип Lazy<SingletonBase<T>>
, Следовательно, вы не можете пройти T
экземпляр в его конструктор.
Единственным вариантом здесь является передача делегата, который возвращает экземпляр SingletonBase<T>
тип (так как SingletonBase<T>
абстрактно). Очевидно, вам нужен способ получить этот экземпляр. А теперь обратите внимание, что это код получения статического свойства в абстрактном классе.
Как вы планируете это сделать?
Вы не можете переопределить статический метод или свойство.
Во-вторых, какова прибыль от синглтона, который оборачивает Lazy<T>
? Почему бы просто не создать потокобезопасный Lazy<T>
:
var singleton = new Lazy<YourClass>(true);
или же:
var singleton = new Lazy<YourClass>(SomeMethodThatCreatesYourClass, true);
UPD.
Согласно правкам вашего вопроса, вы пытаетесь смешать две обязанности:
- создание одноэлементного объекта;
- создание
T
объекты из базы данных.
Это не способ пойти. Вам нужен базовый класс фабрики (или даже интерфейс, если нет никакой логики, которую можно реализовать в базовом классе):
abstract class Factory<T>
{
public abstract IEnumerable<T> For(string systemLangCode);
}
Затем вам нужно создать несколько потомков, например:
class Factory1 : Factory<YouClass1> {}
class Factory2 : Factory<YouClass2> {}
// etc
Затем, если вы хотите, чтобы они были синглетонами, вам просто нужно обернуть отдельные фабрики в Lazy<T>
экземпляры, например:
static class Singletons
{
private static readonly Lazy<Factory1> factory1;
private static readonly Lazy<Factory2> factory2;
static Singletons()
{
factory1 = new Lazy<Factory1>(true); // or Lazy<Factory1>(() => /* */, true)
factory2 = new Lazy<Factory2>(true); // or Lazy<Factory2>(() => /* */, true)
}
public Factory1 Factory1
{
get { return factory1.Value; }
}
public Factory2 Factory2
{
get { return factory2.Value; }
}
// etc
}
и использовать их так:
Singletons.Factory1.For("ab-CD")
Singletons.Factory2.For("ef-GH")
Lazy<T>
сама по себе является одноэлементной реализацией с отложенной инициализацией. Со вторым логическим параметром true
это потокобезопасная реализация. Следовательно, вам не нужно оборачивать его, не нужно писать все эти блокировки и дважды проверять код.