Search code examples
c#genericssingletonabstract-base-class

Generic abstract base class for singletons (C#) - cannot instantiate the private Lazy instance


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?


Solution

  • 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.