4

I currently have a collection of 6 or 7 singletons, all of which do almost the same thing (see the For method in the below example) but with a different internal DB query and return a collection of different objects (so parsing the DB results is different in each singleton).

Therefore, using this question as my base, I've been trying to build an abstract generic base class in C# for these singletons.

There are similar questions on SO but none implement Lazy, which I wish to.

So far I have this

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);
}

However, a problem occurs on the line

_lazy = new Lazy<SingletonBase<T>>(new T());

Visual Studio tells me "Cannot resolve constructor 'Lazy<T>'."

I'm unsure what should be passed into the constructor for Lazy<SingletonBase<T>>, or have I gone in the wrong direction?

Community
  • 1
  • 1
awj
  • 7,482
  • 10
  • 66
  • 120
  • why even use generics? Is there a reason for that? – T_D Aug 07 '14 at 10:37
  • Some standard techniques for doing singletons are shown here: http://csharpindepth.com/Articles/General/Singleton.aspx – dbc Aug 07 '14 at 10:38
  • @T_D: that's a valid question and I don't truly have an answer, other than when I was looking around for other singleton base classes the other examples used generics. – awj Aug 07 '14 at 10:46
  • @dbc: I've already read that article - more than once - but it doesn't cover using it as a derivable base class. – awj Aug 07 '14 at 10:47
  • @awj: "it doesn't cover using it as a derivable base class" - please, tell us, what singleton-related functional are you going to derive and override? – Dennis Aug 07 '14 at 10:52
  • @Dennis: I've updated the OP with more of an explanation. – awj Aug 07 '14 at 10:58
  • I wouldn't use inheritance here. The boilerplate for a `Lazy` based singleton is so short that I wouldn't worry about repetition. – CodesInChaos Aug 07 '14 at 11:20
  • `Lazy` already supports locking, so no need for your lock. Pretty much the whole point of that class is that it abstracts locking away. – CodesInChaos Aug 07 '14 at 11:22
  • a static constructor would achieve the same as a Lazy class in your case here – T_D Aug 07 '14 at 11:28
  • I also want to note that you should not access the database in classic singletons. Database accessing singletons should be IoC singletons instead. – CodesInChaos Aug 07 '14 at 11:28

2 Answers2

1

or have I gone in the wrong direction?

Looks like this.

First of all, _lazy field has a type of Lazy<SingletonBase<T>>. Hence, you can't pass T instance into its constructor.

The only option here is to pass a delegate, which returns instance of SingletonBase<T>-derived type (since SingletonBase<T> is abstract). Obviously, you need a way to obtain this instance. And now note, that this is a code of a static property getter in abstract class.

How do you plan to do this?
You can't override static method or property.

The second, what's the profit from a singleton, which wraps Lazy<T>? Why just don't create thread-safe Lazy<T>:

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

or:

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

UPD.

According to edits of your question, you are trying to mix two responsibilities:

  • the creation of singleton object;
  • the creation of T objects from database.

This isn't a way to go. You need a base factory class (or even an interface, if there's no any logic, that could be implemented in base class):

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

Then, you need to create a number of descendants, e.g.:

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

Then, if you want them to be the singletons, you just have to wrap particular factories into Lazy<T> instances, e.g.:

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
}

and use them this way:

Singletons.Factory1.For("ab-CD")
Singletons.Factory2.For("ef-GH")

Lazy<T> itself is a singleton implementation with lazy initialization. With the second boolean parameter true it is a thread-safe implementation. Hence, you don't need to wrap it, don't need to write all this locks and double-checking code.

Dennis
  • 37,026
  • 10
  • 82
  • 150
  • It's not a very constructive post when you answer with two more questions. You might have a good point but I'm not clear what you're explaining. Could you fill out your example a bit more please? – awj Aug 07 '14 at 10:53
1

Despite some great advice, I still wanted to use a singleton to provide a solution because I felt I could so this in less and simpler code. And here is what I did.

Here is the generic base class.

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);
}

What I'm actually doing in here, aside from instantiating and caching an instance of the derived type, S, is handling requests for a collection of T. Rather than querying the DB for, and storing all possible recordsets, I query for a recordset when needed, using systemLangCode as a variable in the query, parse this recordset into a List<T>, and then store this collection in Dictionary using systemLangCode as the key.

Therefore, a query is only made if this resultset has not been previously obtained by that singleton.

And here is a derivation of the above base class.

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);
    }
}

This base class now allows me to build a series of derived classes which contain little more than the database query. Thus, I now have a single definition of a singleton which I can use for storing, for example, a collection of languages in different languages, a collection of countries in different languages, a collection of timezones in different languages, etc.

An example of usage:

var langsDe = Languages.Instance.For("de");
var langsEn = Languages.Instance.For("en");

And if I later call

var langsDe = Languages.Instance.For("de");

Then this will be retrieved from the internal Dictionary.

awj
  • 7,482
  • 10
  • 66
  • 120