具有可触发初始化的C#Singleton模式
我需要一个单身人士:
- 很懒
- 是线程安全的
- 在施工时加载一些值
- 可以随时查询这些值
- 初始化可能在查询开始之前的某个精确时间发生 – 所以我必须能够以某种方式从外部触发它。 当然,多次触发应该只进行一次初始化。
我使用.NET 3.5。
我使用静态子类开始使用Jon Skeet的实现 (第5版):
public sealed class Singleton { IEnumerable 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(); } }
除了“从外部触发初始化”之外,几乎所有方框都会打勾。 由于实际初始化发生在ctor内部,因此不会发生多次。
如何实现这一目标?
单例将使用如下:
public static void Main(){ //do stuff, singleton should not yet be initialized. //the time comes to initialize the singleton, eg 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; }
好像你可以这样做:
public sealed class Singleton { IEnumerable 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 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 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(); } } } }
您可以设置一个可以从外部触发的Initialize方法,如果您需要稍后进行初始化,但如果每次触发时值都不同,则它不能是静态的,这违反了Singleton模式。
根据你的例子,它没有变量,我假设你只是在初始化发生时延迟(例程而不是构造函数),但你的问题表明你想要不同的值,但如果多个初始化发生在一起,它只会初始化一次,所以我对此感到有点困惑。
我不确定你是否只需要Singleton implmentation,但是如果没有关于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 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 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 initialisationAction) { lock(typeof(Singleton )) { if (instance != null) { return; } instance = new T(); initialisationAction(instance); } } }
我的第一个想法就是使用分配给单例实例的一次性变量,它会(可能?)触发初始化
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; }
抛开编译器优化,我认为这涉及所有要求。