C# Singleton pattern with triggerable initialization

前端 未结 6 1767
遥遥无期
遥遥无期 2021-02-09 09:45

I need a singleton that:

  • is lazy loaded
  • is thread safe
  • loads some values at construction
  • those values can be queried at any time
相关标签:
6条回答
  • 2021-02-09 10:03
    public class Singleton<T> where T : class, new()
    {
        private static T instance;
        public static T Instance
        {
            get
            {
                if (instance == null)
                {
                    throw new Exception("singleton needs to be initialised before use");
                }
                return instance;
            }
        }
        public static void Initialise(Action<T> initialisationAction)
        {
            lock(typeof(Singleton<T>))
            {
                if (instance != null)
                {
                    return;
                }
                instance = new T();
                initialisationAction(instance);
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-09 10:04

    The first idea I had was to just use a throwaway variable assigned to the singleton's instance, which would (probably?) trigger the initialization

    static Main() 
    {
        var unused = Singleton.Instance;
        //this should initialize the singleton, unless the compiler optimizes it out.
        //I wonder if the compiler is smart enough to see this call has side effects.
    
        var vals = Singleton.Instance.Values;
    }
    

    ... but programming by side-effects is something I try hard to avoid, so let's make the intention a bit clearer.

    public class Singleton {
        public static void Initialize() {
            //this accesses the static field of the inner class which triggers the private Singleton() ctor.  
            Instance._Initialize();
        }
        private void _Initialize()
        { //do nothing
        }
    
        [the rest as before]
    }
    

    so the usage would be:

    static Main() 
    {
        //still wondering if the compiler might optimize this call out
        Singleton.Initialize();
    
        var vals = Singleton.Instance.Values;
    }
    

    Btw this would also work:

    static Main() 
    {
        var vals = Singleton.Instance.Values;
    }
    

    Compiler optimization aside, I think this deals with all the requirements.

    0 讨论(0)
  • 2021-02-09 10:05

    You can do something like this

    public sealed class Singleton
    {
        IEnumerable<string> Values { get; set; }
    
        private Singleton()
        {
            Console.WriteLine("-- Private Singleton constructor");
            Values = new[] { "quick", "brown", "fox" };
        }
    
        public static Singleton Instance
        {
            get
            {
                Console.WriteLine("- Singleton Instance");
                return Nested.instance;
            }
        }
    
        public static void Initialize()
        {
            Console.WriteLine("- Singleton Initialize");
            Nested.Initialize();
        }
    
        internal class Nested
        {
            private static object syncRoot = new object();
            // Explicit static constructor to tell C# compiler
            // not to mark type as beforefieldinit
            static Nested()
            {
                Console.WriteLine("-- Static Nested constructor");
            }
    
            internal static readonly Singleton instance = new Singleton();
    
            internal static void Initialize()
            {
                lock (syncRoot)
                {
                    Console.WriteLine("-- Locked");
                    Console.WriteLine("--- Nested Initialize");
                    Console.WriteLine("-- Unlocked");
                }
            }
        }
    }
    

    Usage

    class Program
    {
        static void Main(string[] args)
        {
            var i = Singleton.Instance;
            i = Singleton.Instance;
    
            Console.WriteLine("-----");
    
            Singleton.Initialize();
            Singleton.Initialize();
            Singleton.Initialize();
    
            Console.Read();
        }
    }
    

    Which outputs

    - Singleton Instance
    -- Private Singleton constructor
    -- Static Nested constructor
    - Singleton Instance
    -----
    - Singleton Initialize
    -- Locked
    --- Nested Initialize
    -- Unlocked
    - Singleton Initialize
    -- Locked
    --- Nested Initialize
    -- Unlocked
    - Singleton Initialize
    -- Locked
    --- Nested Initialize
    -- Unlocked
    
    0 讨论(0)
  • 2021-02-09 10:05

    You can use double-checked locking pattern. Just add following code in you Singleton class:

    public sealed class Singleton
    {
       ..........................
    
            private static object locker = new object();
            private static bool initialized = false;
    
            public static void Initialize() {
               if (!initialized){ 
                 lock(locker) {
                    if (!initialized){ 
                      //write initialization logic here
                      initialized = true;
                     }
                  }
                }
            }
    
    .......................
    
    }
    
    0 讨论(0)
  • 2021-02-09 10:09

    Seems like you could do:

    public sealed class Singleton
    {
        IEnumerable<string> Values {get; private set;}
        private Singleton(bool loadDefaults)
        {
            if (loadDefaults)
                Values = new[]{"quick", "brown", "fox"};
            else
                Values = new[]{"another", "set", "of", "values"};
        }
    
        public static Singleton Instance { get { return Nested.instance; } }
    
        public static void Initialize() {
            Nested.Initialize();
        }
    
        private class Nested
        {
            // Explicit static constructor to tell C# compiler
            // not to mark type as beforefieldinit
            static Nested()
            {
            }
    
            internal static readonly Singleton instance = new Singleton(true);
            private static object instanceLock = new object();
            private static bool isInitialized = false; 
    
            public static void Initialize() {
                lock(instanceLock) {
                    if (!isInitialized) {
                        isInitialized = true;
                        instance = new Singleton(false);
                    }
                }
            }
    
        }
    } 
    

    Or to create a single instance that will be updated:

    public sealed class Singleton
    {
        IEnumerable<string> Values {get; private set;}
        private Singleton()
        {
            Values = new[]{"quick", "brown", "fox"};
        }
    
        public static Singleton Instance { get { return Nested.instance; } }
    
        private static object instanceLock = new object();
        private static bool isInitialized = false; 
    
        public static void Initialize() {
            lock(instanceLock) {
                if (!isInitialized) {
                    isInitialized = true;
                    Instance.Values = new[]{"another", "set", "of", "values"};
                }
            }
        }
    
        private class Nested
        {
            // Explicit static constructor to tell C# compiler
            // not to mark type as beforefieldinit
            static Nested()
            {
            }
    
            internal static readonly Singleton instance = new Singleton();
        }
    } 
    

    And the third variation based on your immutable comment and removal of Nested class comment:

    public sealed class Singleton
    {
        IEnumerable<string> Values {get; private set;}
        private Singleton()
        {
            Values = new[]{"quick", "brown", "fox"};
        }
    
        private static Singleton instance;
        private static object instanceLock = new object();
    
        public static Singleton Instance {
            get {
                Initialize();
                return instance;
            }
         }
    
        public static void Initialize() {
            if (instance == null) {
                lock(instanceLock) {
                    if (instance == null)
                        instance = new Singleton();
                }
            }
        }
    } 
    
    0 讨论(0)
  • 2021-02-09 10:20

    You can set up an Initialize method that can be fired from outside, if you need the initialize to happen later, but if the values are different on each time it is fired, then it cannot be static, which violates the Singleton pattern.

    Based on your example, which has no variables, I assume you are just delaying when the initialization happens (routine rather than constructor), but your question suggests you want different values, but if multiple initializations happen close together, it only initializes once, so I am a bit confused on this.

    I am not sure you need a Singleton only implmentation, but cannot fully answer without information on whether or not the Initialize() runs the same code every time or has some type of variable nature.

    0 讨论(0)
提交回复
热议问题