單例模式(Singleton Pattern)意圖明確:保證一個類別僅有一個實例,並提供一個全域存取點(GoF)。這不是「如何創建物件」,而是「如何限制物件數量」。在 .NET 中,根據執行緒安全性、延遲初始化需求、效能考量,有五種經典實現方式。
public sealed class Singleton {
private static Singleton _instance = null;
private Singleton() { }
public static Singleton Instance {
get {
if (_instance == null) _instance = new Singleton();
return _instance;
}
}
}
缺陷:多執行緒同時判斷 _instance == null 為真,導致創建多個實例。僅適用於單執行緒或啟動階段初始化場景。
public sealed class Singleton {
private static Singleton _instance = null;
private static readonly object _lock = new object();
private Singleton() { }
public static Singleton Instance {
get {
lock (_lock) {
if (_instance == null) _instance = new Singleton();
return _instance;
}
}
}
}
每次存取都加鎖,效能開銷大。雙重檢查鎖定(DCL)在 .NET 記憶體模型下需配合 volatile 或 Thread.MemoryBarrier,易出錯,不推薦手寫 DCL。
public sealed class Singleton {
private static readonly Singleton _instance = new Singleton();
static Singleton() { } // 顯式靜態建構子觸發延遲初始化(BeforeFieldInit 控制)
private Singleton() { }
public static Singleton Instance => _instance;
}
原理:CLR 保證靜態欄位初始化的執行緒安全性;readonly 確保唯一賦值;sealed 禁止繼承破壞單例。這是 .NET 中最推薦的標準實現。
public sealed class Singleton {
private Singleton() { }
public static Singleton Instance => Nested._instance;
private class Nested {
static Nested() { }
internal static readonly Singleton _instance = new Singleton();
}
}
優勢:利用巢狀類別的靜態建構子特性,只有首次存取 Nested._instance 時才初始化。若單例建構耗時/耗資源且不一定被用到,此版本完美平衡「延遲」與「安全」。
public sealed class Singleton {
private static readonly Lazy<Singleton> _instance =
new Lazy<Singleton>(() => new Singleton(), LazyThreadSafetyMode.ExecutionAndPublication);
private Singleton() { }
public static Singleton Instance => _instance.Value;
}
優勢:框架級支援、語義清晰、可選執行緒安全模式、支援例外傳播與重試。現代 .NET 專案首選。
public sealed class CounterSingleton {
private static readonly CounterSingleton _instance = new CounterSingleton();
private int _count = 0;
private CounterSingleton() { Thread.Sleep(2000); } // 模擬耗時初始化
public static CounterSingleton Instance => _instance;
public void Increment() => Interlocked.Increment(ref _count);
public int Value => _count;
}
public class Worker {
public void DoWork() {
for (int i = 0; i < 4; i++) {
var c = CounterSingleton.Instance;
c.Increment();
Console.WriteLine($"{Thread.CurrentThread.Name} -> Count: {c.Value}");
}
}
}
// 四個執行緒並發,最終計數必為 16,且只有一個實例被創建
| 優點 | 缺點 |
|---|---|
| 實例控制:嚴格保證唯一性 | 開銷:每次存取屬性有微小檢查成本(靜態初始化版幾乎為零) |
| 靈活性:類別掌控實例化過程 | 開發困惑:使用者可能嘗試 new 失敗,需文檔說明 |
| 全域存取:方便跨模組共享狀態 | 生命週期管理:無法主動銷毀(GC 回收前常駐),單元測試難隔離 |
| 延遲初始化:按需創建節省資源 | 隱藏相依:類別間隱性耦合,違反顯式相依原則 |
WellKnownObjectMode.Singletonservices.AddSingleton<T>()),建構子注入顯式相依Lazy<T>ISerializable 或 IDeserializationCallback 回傳現有實例HttpContext.Current(舊版 ASP.NET)ConfigurationManager.AppSettingsTrace.ListenersTaskScheduler.DefaultSynchronizationContext.Current單例模式看似簡單,實則蘊含「執行緒安全」、「延遲初始化」、「記憶體模型」、「生命週期管理」等深層議題。在 .NET 中,靜態初始化版與 Lazy<T> 版是兩大金標準,前者極簡高效,後者語義現代且功能豐富。但更重要的現代觀點是:單例應作為實現細節隱藏在 DI 容器之後,而非洩漏為公共靜態屬性。讓容器管理單例生命週期,類別專注業務邏輯,這才是 .NET Core/5+ 時代的正統之道。