Общий абстрактный базовый класс для синглетонов (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 это потокобезопасная реализация. Следовательно, вам не нужно оборачивать его, не нужно писать все эти блокировки и дважды проверять код.

Другие вопросы по тегам