4

我目前有一个 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<T>'。”

我不确定应该将什么传递给构造函数Lazy<SingletonBase<T>>,还是我走错了方向?

4

2 回答 2

1

还是我走错了方向?

看起来像这样。

首先,_lazyfield 的类型为Lazy<SingletonBase<T>>. 因此,您不能将T实例传递给它的构造函数。

这里唯一的选择是传递一个委托,它返回SingletonBase<T>派生类型的实例(因为SingletonBase<T>是抽象的)。显然,您需要一种方法来获取此实例。现在请注意,这是抽象类中静态属性 getter 的代码。

你打算怎么做?
您不能覆盖静态方法或属性。

第二,单例的利润是Lazy<T>多少?为什么不创建线程安全Lazy<T>

var singleton = new Lazy<YourClass>(true);

或者:

var singleton = new Lazy<YourClass>(SomeMethodThatCreatesYourClass, true);

升级版

根据您对问题的编辑,您正在尝试混合两种职责:

  • 创建单例对象;
  • 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,它是一个线程安全的实现。因此,您不需要包装它,不需要编写所有这些锁和双重检查代码。

于 2014-08-07T10:29:54.213 回答
1

尽管有一些很好的建议,我仍然想使用单例来提供解决方案,因为我觉得我可以用更少和更简单的代码来做到这一点。这就是我所做的。

这是通用基类。

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在查询中将其用作变量,将此记录集解析为 a List<T>,然后将此集合存储为DictionaryusingsystemLangCode作为键。

因此,仅当该单例先前未获得此结果集时才进行查询。

这是上述基类的派生。

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

于 2014-08-07T14:52:16.477 回答