首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

單例模式陷阱:靜態唯讀欄位初始化異常導致自訂例外被吞沒的解決方案

     編輯:關於C語言

現象重現

使用 C# 常見的「靜態唯讀欄位」實作單例:

public sealed class Singleton
{
    public static readonly Singleton Instance = new Singleton(); // 靜態建構式
    private Singleton()
    {
        new TriggerException().ComplicatedCalculate(); // 拋出自訂例外
    }
}

客戶端呼叫 Singleton.Instance 時,預期捕獲 CustomException,實際卻收到 TypeInitializationException,InnerException 才是真正的自訂例外。

根本原因分析

  • 靜態欄位初始化 由 CLR 在類型首次存取前自動執行,屬於類型初始化 階段。
  • 若初始化過程拋出例外,CLR 會將其包裝為 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);
}

進階補充:.NET 4+ Lazy<T> 寫法

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>)可確保例外正常傳播,利於錯誤診斷與日誌記錄。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved