傳統錯誤處理依賴函式傳回值(如 bool、HRESULT),缺點明顯:呼叫端容易忽略檢查、錯誤資訊貧乏、無法自動傳播。.NET 執行期採用例外處理作為核心錯誤機制,優勢在於:
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] 並實作 ISerializablefinally 確保清理代碼必定執行,即使發生 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()
不同於 C++ 需追蹤所有區域物件解構,.NET 由 GC 管理物件生命週期。未拋出例外時,try 區塊幾乎零開銷;僅在例外發生時才付出堆疊追蹤成本。因此:
File.Exists 或 TryParse 模式ArgumentException 衍生類InvalidOperationExceptionIDisposable / using 管理非受控資源