單例模式 是最廣為人知的設計模式,但傳統實作有個痛點:**每個單例類別都要重寫相同的樣板程式碼** (私有建構子、靜態實例欄位、公開靜態 Instance 屬性、執行緒安全鎖定)。C# 2.0 引入泛型後,我們可以將這些共同邏輯抽離為 **`SingletonProvider
// 每個單例類別都要寫這麼多重複代碼
public sealed class Logger
{
private static readonly Logger _instance = new Logger();
private static readonly object _lock = new();
private Logger() { }
public static Logger Instance
{
get
{
// 雙重檢查鎖定
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
return _instance; // 實際上靜態初始化已保證執行緒安全
}
}
return _instance;
}
}
public void Log(string msg) => Console.WriteLine($"[LOG] {msg}");
}
using System;
///
/// 通用單例提供者:為任意類別提供單例存取
///
/// 目標類別,必須有公用無參數建構子
public class SingletonProvider where T : new()
{
// 私有建構子防止外部實例化
private SingletonProvider() { }
// 延遲初始化靜態實例 (C# 保證執行緒安全)
private static readonly Lazy _instance = new Lazy(() => new T());
///
/// 取得單例實例 (執行緒安全、延遲初始化)
///
public static T Instance => _instance.Value;
}
///
/// 進階單例提供者:支援參數化建構與初始化委派
///
public class SingletonProvider where T : class
{
private SingletonProvider() { }
// 使用 Lazy 確保執行緒安全與延遲初始化
private static readonly Lazy _instance = new Lazy(CreateInstance, true);
// 建立實例的委派 (可在首次存取前設定)
private static Func _factory = () => Activator.CreateInstance();
///
/// 設定自定建立邏輯 (必須在首次存取 Instance 前呼叫)
///
public static void Configure(Func factory)
{
if (_instance.IsValueCreated)
throw new InvalidOperationException("單例已建立,無法重新配置");
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
}
///
/// 重設單例 (僅供測試使用)
///
internal static void ResetForTesting()
{
// 注意:Lazy 無法重設,測試時建議使用介面抽象而非直接依賴 SingletonProvider
}
private static T CreateInstance() => _factory();
public static T Instance => _instance.Value;
}
// 使用範例:參數化建構
public class DatabaseConnection
{
public string ConnectionString { get; }
public int TimeoutSeconds { get; }
public DatabaseConnection(string connStr, int timeout)
{
ConnectionString = connStr;
TimeoutSeconds = timeout;
}
}
// 應用啟動時配置
SingletonProvider.Configure(() =>
new DatabaseConnection("Server=.;Database=MyDB;Trusted_Connection=True;", 30));
// 之後直接使用
var conn = SingletonProvider.Instance;
Console.WriteLine($"連線字串: {conn.ConnectionString}");
// 一般類別:只需加上無參數建構子
public class TestClass
{
private readonly string _createdTimestamp;
public TestClass()
{
_createdTimestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
}
public void Write() => Console.WriteLine($"建立時間: {_createdTimestamp}");
}
// 客戶端程式碼
public class Program
{
public static void Main()
{
// 多執行緒測試
var tasks = Enumerable.Range(1, 100).Select(i => Task.Run(() =>
{
var instance = SingletonProvider.Instance;
instance.Write();
})).ToArray();
Task.WaitAll(tasks);
// 驗證所有執行緒取得同一實例
Console.WriteLine("所有執行緒輸出的時間戳記應完全相同");
}
}
| 特性 | Lazy<T> 行為 | 單例需求 |
|---|---|---|
| 執行緒安全 | 預設 `LazyThreadSafetyMode.ExecutionAndPublication` 保證 | 多執行緒環境下只建立一個實例 |
| 延遲初始化 | 首次存取 `.Value` 時才建立 | 避免啟動時不必要的建立開銷 |
| 例外快取 | 建立失敗會快取例外,後續存取重擲 | 避免重複嘗試建立失敗的實例 |
| 效能 | 內部使用雙重檢查鎖定最佳化 | 高併發下極低開銷 |
| 方式 | 執行緒安全 | 延遲初始化 | 程式碼重用 | 備註 |
|---|---|---|---|---|
| 靜態 readonly 欄位 | ✅ (CLR 保證) | ❌ (型別載入時初始化) | ❌ 每類別重寫 | 最簡單,適用大多數情況 |
| 雙重檢查鎖定 | ✅ (需 volatile) | ✅ | ❌ 每類別重寫 | 易寫錯,.NET 已不推薦 |
| Lazy<T> 包裝 | ✅ | ✅ | ❌ 每類別重寫 | 現代 .NET 推薦手寫方式 |
| SingletonProvider<T> | ✅ | ✅ | ✅ 寫一次通用 | 本文方案,最佳重用性 |
| DI 容器 (Singleton 生命週期) | ✅ | ✅ | ✅ 架構級重用 | 企業級應用首選 |
public class Base { }
public class Derived : Base { }
// 這會建立兩個獨立單例!
var baseInstance = SingletonProvider .Instance;
var derivedInstance = SingletonProvider.Instance;
// baseInstance != derivedInstance (不同型別參數 = 不同靜態欄位)
// ASP.NET Core / .NET Generic Host 內建 DI
public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message) => Console.WriteLine($"[LOG] {message}");
}
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// 註冊為 Singleton 生命週期 (容器管理單例)
builder.Services.AddSingleton();
// 或註冊工廠委派
builder.Services.AddSingleton(sp =>
new FileLogger("app.log", sp.GetRequiredService()));
// 控制器/服務透過建構子注入 (無單例樣板碼)
public class OrderService
{
private readonly ILogger _logger;
public OrderService(ILogger logger) => _logger = logger;
public void Process() => _logger.Log("處理訂單...");
}
| 場景 | 推薦方案 |
|---|---|
| ASP.NET Core / Worker Service / 現代 .NET 應用 | DI 容器 (AddSingleton) |
| Legacy WinForms / WPF / Console 應用 (無 DI) | SingletonProvider<T> |
| 函式庫/套件開發 (不想強制使用者用 DI) | SingletonProvider<T> + 可選 DI 整合 |
| 單元測試專案 | 介面 + Mock / 測試用 DI 容器 |
| 需支援參數化建構、重設、多租戶 | 進階 SingletonProvider 或 DI |
C# 2.0 泛型讓 **`SingletonProvider
但在現代 .NET 架構中,**依賴注入容器的 Singleton 生命週期** 才是企業級應用的標準解法。`SingletonProvider