【问题标题】:Generic abstract base class for singletons (C#) - cannot instantiate the private Lazy instance单例的通用抽象基类 (C#) - 无法实例化私有 Lazy 实例
【发布时间】:2014-08-07 10:24:34
【问题描述】:

我目前有 6 或 7 个单例的集合,所有这些都做几乎相同的事情(参见下面示例中的 For 方法)但使用不同的内部数据库查询并返回不同对象的集合(所以在每个单例中解析数据库结果是不同的)。

因此,使用this question 作为我的基础,我一直在尝试在 C# 中为这些单例构建一个抽象的通用基类。

SO 上有 similar questions,但没有一个实现 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'。”

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

【问题讨论】:

  • 为什么还要使用泛型?这是有原因的吗?
  • 这里展示了一些做单例的标准技术:csharpindepth.com/Articles/General/Singleton.aspx
  • @T_D:这是一个有效的问题,我没有真正的答案,除了当我四处寻找其他单例基类时,其他示例使用泛型。
  • @dbc:我已经读过那篇文章——不止一次——但它没有涵盖将它用作可派生的基类。
  • @awj:“它不包括将其用作可派生的基类” - 请告诉我们,您将派生和覆盖哪些与单例相关的函数?

标签: c# generics singleton abstract-base-class


【解决方案1】:

还是我走错了方向?

看起来像这样。

首先,_lazy 字段的类型为Lazy&lt;SingletonBase&lt;T&gt;&gt;。因此,您不能将 T 实例传递给它的构造函数。

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

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

第二个,单例封装Lazy&lt;T&gt;的利润是多少? 为什么不创建线程安全的Lazy&lt;T&gt;

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

或:

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

UPD

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

  • 单例对象的创建;
  • 从数据库创建T 对象。

这不是一条路。 你需要一个 factory 基类(或者甚至是一个接口,如果没有任何逻辑,可以在基类中实现):

abstract class Factory<T>
{
    public abstract IEnumerable<T> For(string systemLangCode);
}

然后,你需要创建一些后代,例如:

class Factory1 : Factory<YouClass1> {}
class Factory2 : Factory<YouClass2> {}
// etc

然后,如果您希望它们成为单例,您只需将特定工厂包装到 Lazy&lt;T&gt; 实例中,例如:

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&lt;T&gt; 本身具有延迟初始化的单例实现。使用第二个布尔参数true,它是一个线程安全的实现。因此,您不需要包装它,不需要编写所有这些锁和双重检查代码。

【讨论】:

  • 当您再回答两个问题时,这不是一篇很有建设性的帖子。你可能有一个很好的观点,但我不清楚你在解释什么。请您再填写一下您的示例吗?
【解决方案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&lt;T&gt;,然后将此集合存储在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中检索。

【讨论】:

    猜你喜欢
    • 2012-09-08
    • 1970-01-01
    • 1970-01-01
    • 2015-08-04
    • 1970-01-01
    • 1970-01-01
    • 2015-10-06
    • 1970-01-01
    相关资源
    最近更新 更多