使用 C# 常見的「靜態唯讀欄位」實作單例:
public sealed class Singleton
{
public static readonly Singleton Instance = new Singleton(); // 靜態建構式
private Singleton()
{
new TriggerException().ComplicatedCalculate(); // 拋出自訂例外
}
}
客戶端呼叫 Singleton.Instance 時,預期捕獲 CustomException,實際卻收到 TypeInitializationException,InnerException 才是真正的自訂例外。
TypeInitializationException,原始例外存放於 InnerException。TypeInitializationException,不再重試初始化。get 存取器編譯為 IL 方法,例外可正常冒泡至呼叫端,不會被包裝。public sealed class Singleton
{
private static volatile Singleton _instance;
private static readonly object _lock = new object();
public static Singleton Instance
{
get
{
if (_instance == null) // 第一次檢查(無鎖,效能)
{
lock (_lock) // 進入關鍵區
{
if (_instance == null) // 第二次檢查(防重複建立)
{
_instance = new Singleton(); // 此處例外可正常拋出
}
}
}
return _instance;
}
}
private Singleton()
{
new TriggerException().ComplicatedCalculate(); // 自訂例外可被外層 catch
}
}
try
{
var s = Singleton.Instance; // 此處可 catch 到 CustomException
}
catch (CustomException ex)
{
Console.WriteLine("成功捕獲自訂例外: " + ex.Message);
}
catch (TypeInitializationException ex)
{
Console.WriteLine("被包裝的例外: " + ex.InnerException?.Message);
}
public sealed class Singleton
{
private static readonly Lazy _lazy =
new Lazy(() => new Singleton(), LazyThreadSafetyMode.ExecutionAndPublication);
public static Singleton Instance => _lazy.Value;
private Singleton() { ... }
}
Lazy<T> 內建雙重檢查與例外快取機制,首次存取 Value 拋出的例外會被快取,後續存取重拋相同例外,行為一致且程式碼極簡潔。
靜態唯讀欄位單例雖簡潔,但初始化例外會被 CLR 包裝為 TypeInitializationException,導致自訂例外被隱藏。採用屬性延遲初始化(雙重檢查鎖或 Lazy<T>)可確保例外正常傳播,利於錯誤診斷與日誌記錄。