带有可触发初始化的c#单例模式

本文关键字:单例模式 初始化 | 更新日期: 2023-09-27 18:04:25

我需要一个单例:

  • 是惰性加载
  • 线程安全
  • 在construction中加载一些值
  • 这些值可以随时查询
  • 初始化可能发生在某个精确的时间,在查询开始之前-所以我必须能够从外部触发它。当然,多次触发应该只初始化一次。

我使用。net 3.5。

我从Jon Skeet使用静态子类的实现(第5版)开始:

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 class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }
        internal static readonly Singleton instance = new Singleton();
    }
} 

这几乎勾起了所有的框,除了"从外部触发初始化"。因为实际的初始化是在actor内部进行的,所以只能进行一次。

如何做到这一点?

单例模式将像这样使用:

public static void Main(){
    //do stuff, singleton should not yet be initialized.
    //the time comes to initialize the singleton, e.g. a database connection is available
    //this may be called 0 or more times, possibly on different threads
    Singleton.Initialize();
    Singleton.Initialize();
    Singleton.Initialize();
    //actual call to get retrieved values, should work
    var retrieveVals = Singleton.Instance.Values;
}

带有可触发初始化的c#单例模式

似乎你可以这样做:

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

或者创建将被更新的单个实例:

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

基于不可变注释和移除嵌套类注释的第三种变体:

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

我的第一个想法是使用分配给单例实例的一次性变量,这将(可能?)触发初始化

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

…但是副作用编程是我努力避免的,所以让我们把目的讲得更清楚一些。

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]
}

所以用法是:

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

顺便说一句,这个也可以:

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

除了编译器优化之外,我认为这处理了所有的要求。

如果需要稍后进行初始化,可以设置一个Initialize方法,该方法可以从外部触发,但如果每次触发的值都不同,那么它就不能是静态的,这违反了Singleton模式。

根据你的例子,它没有变量,我假设你只是延迟初始化发生(例程而不是构造函数),但你的问题表明你想要不同的值,但如果多个初始化发生在一起,它只初始化一次,所以我对此有点困惑。

我不确定您是否需要单例实现,但如果没有关于Initialize()是否每次运行相同的代码或具有某种类型的变量性质的信息,则无法完全回答

可以使用双重检查锁定模式。只需在Singleton类中添加以下代码:

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

你可以这样做

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");
            }
        }
    }
}
使用

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

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