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

C# 例外處理深度解析:從 try-catch 到自訂例外的最佳實務

     編輯:關於C語言

為什麼不再使用傳回碼?

傳統錯誤處理依賴函式傳回值(如 bool、HRESULT),缺點明顯:呼叫端容易忽略檢查、錯誤資訊貧乏、無法自動傳播。.NET 執行期採用例外處理作為核心錯誤機制,優勢在於:

  • 無法被靜默忽略,未處理會終止程式並顯示錯誤
  • 攜帶豐富上下文(堆疊追蹤、內部例外、自訂資料)
  • 支援跨語言、跨組件的統一錯誤模型

基礎語法:try / catch / finally

try
{
    // 可能拋出例外的代碼
    int result = 10 / 0;
}
catch (DivideByZeroException ex)
{
    // 處理特定例外
    Console.WriteLine($"除零錯誤: {ex.Message}");
}
catch (Exception ex)
{
    // 捕獲所有其他例外(建議最後放置)
    Console.WriteLine($"未預期錯誤: {ex}");
}
finally
{
    // 無論是否發生例外皆會執行,適合釋放資源
    Console.WriteLine("清理資源...");
}

例外捕獲順序原則

由特殊到一般:先捕獲衍生類別(如 DivideByZeroException),再捕獲基底類別(Exception)。反向排序會導致編譯錯誤(CS0160)。

例外傳播與呼叫堆疊展開

當方法內未捕獲例外,執行期會沿著呼叫堆疊向上尋找匹配的 catch 區塊:

void A() { B(); }
void B() { C(); }
void C() { throw new InvalidOperationException("錯誤"); }

try { A(); }
catch (InvalidOperationException ex) { /* 在此處捕獲 */ }

三種重新拋出策略比較

策略做法適用場景缺點
Caller Beware不捕獲,直接讓例外冒泡無法在當前層級處理,需上層決定物件可能處於無效狀態
Caller Confuse捕獲 → 清理 → throw; 重拋原例外需保證物件狀態有效,但無額外資訊呼叫端難以診斷根因
Caller Inform捕獲 → 包裝新例外(含內部例外) → 拋出需提供業務語境資訊略增加程式碼複雜度
// Caller Inform 範例:包裝並保留原始堆疊
catch (DivideByZeroException ex)
{
    throw new InvalidOperationException("計算平均失敗:分母為零", ex);
}

自訂例外類別設計指南

當內建例外無法精確表達業務錯誤時,建立自訂例外:

[Serializable]
public class InsufficientInventoryException : Exception
{
    public int ProductId { get; }
    public int RequestedQuantity { get; }
    public int AvailableQuantity { get; }

    public InsufficientInventoryException(int productId, int requested, int available)
        : base($"產品 {productId} 庫存不足:需求 {requested},可用 {available}")
    {
        ProductId = productId;
        RequestedQuantity = requested;
        AvailableQuantity = available;
    }

    // 序列化建構子(跨 AppDomain/遠端呼叫必備)
    protected InsufficientInventoryException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
        ProductId = info.GetInt32(nameof(ProductId));
        RequestedQuantity = info.GetInt32(nameof(RequestedQuantity));
        AvailableQuantity = info.GetInt32(nameof(AvailableQuantity));
    }

    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue(nameof(ProductId), ProductId);
        info.AddValue(nameof(RequestedQuantity), RequestedQuantity);
        info.AddValue(nameof(AvailableQuantity), AvailableQuantity);
    }
}

命名與繼承規範

  • 類別名以 Exception 結尾
  • 繼承 System.Exception(非 ApplicationException,已棄用)
  • 實作三大標準建構子:無參數、訊息、訊息+內部例外
  • 標記 [Serializable] 並實作 ISerializable

finally 區塊:資源釋放的保證

finally 確保清理代碼必定執行,即使發生 return、break 或例外:

FileStream fs = null;
try
{
    fs = new FileStream("data.txt", FileMode.Open);
    // 讀取處理...
}
finally
{
    fs?.Dispose(); // C# 6.0+ null 條件運算子
}

// 更優雅的 using 模式(編譯器自動產生 try/finally)
using (var fs = new FileStream("data.txt", FileMode.Open))
{
    // 讀取處理...
} // 此處自動呼叫 Dispose()

效能考量:.NET 的零成本例外模型

不同於 C++ 需追蹤所有區域物件解構,.NET 由 GC 管理物件生命週期。未拋出例外時,try 區塊幾乎零開銷;僅在例外發生時才付出堆疊追蹤成本。因此:

  • 正常流程勿用例外控制(如迴圈結束)
  • 預期狀況(如檔案不存在)用 File.Exists 或 TryParse 模式
  • 真正「例外」狀況(Bug、資源耗盡、外部失敗)才拋出

設計準則總結

  1. 提供有意義的錯誤訊息與上下文資料
  2. 僅在真正異常情況拋出例外
  3. 參數驗證失敗拋 ArgumentException 衍生類
  4. 物件狀態不當拋 InvalidOperationException
  5. 優先使用內建例外類別,必要時才自訂
  6. 捕獲不處理的例外時,考慮包裝後重拋
  7. 實作 IDisposable / using 管理非受控資源
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved