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

C# 例外處理核心技術:溢位檢查、try-catch-finally、自訂例外與最佳實務指南

     編輯:關於C語言

溢位檢查:checked / unchecked 與編譯器選項

C# 整數運算預設不檢查溢位(效能優先),溢位會悄悄截斷:

// 預設行為:溢位不拋出例外,結果截斷
int max = int.MaxValue; // 2,147,483,647
int result = max + 1;   // -2,147,483,648 (環繞)
Console.WriteLine(result); // 無任何警告

啟用溢位檢查的三種方式

方式範圍語法適用場景
編譯器選項整個組件csc /checked+ Program.cs專案層級強制啟用
checked 陳述式區塊/運算式checked { i = j * k; } 或 checked(j * k)局部關鍵計算
unchecked 陳述式區塊/運算式unchecked { i = j * k; }在 /checked+ 專案中排除特定運算
// 範例:階乘計算與溢位處理
long factorial = 1;
int n = 20; // 20! > long.MaxValue

try
{
    checked // 區塊層級啟用
    {
        for (int i = 1; i <= n; i++)
        {
            factorial *= i; // 13! 時拋出 OverflowException
        }
    }
}
catch (OverflowException ex)
{
    Console.WriteLine($"溢位!{n}! 超出 long 範圍");
    Console.WriteLine(ex.StackTrace);
}

例外處理核心語法:try / catch / finally

基本結構

try
{
    // 可能拋出例外的代碼
    int result = 10 / 0;
}
catch (DivideByZeroException ex)
{
    // 處理特定例外
    Console.WriteLine($"除零錯誤: {ex.Message}");
}
catch (ArithmeticException ex) when (ex.HResult == -2147024362) // 例外篩選器 C# 6+
{
    // 條件式捕獲
    Console.WriteLine("其他算術錯誤");
}
catch (Exception ex)
{
    // 捕獲所有其他例外(放最後)
    Console.WriteLine($"未預期錯誤: {ex}");
}
finally
{
    // 無論是否發生例外皆執行
    Console.WriteLine("清理資源、關閉連線...");
}

多重 catch 排序原則

由衍生類別到基底類別。反向會導致編譯錯誤 CS0160:「先前的 catch 子句已捕獲所有此類或超類型的例外」。

例外傳播與呼叫堆疊展開

void Level1() { Level2(); }
void Level2() { Level3(); }
void Level3() { throw new InvalidOperationException("底層錯誤"); }

try { Level1(); }
catch (InvalidOperationException ex)
{
    // 此處捕獲,StackTrace 保留完整呼叫鏈
    Console.WriteLine(ex.StackTrace);
}

finally 區塊:資源釋放保證

FileStream fs = null;
try
{
    fs = new FileStream("data.txt", FileMode.Open);
    // 處理檔案...
}
finally
{
    // 即使 try 中有 return、break、throw,finally 仍執行
    fs?.Dispose();
}

// 等價的 using 模式(推薦)
using (var fs = new FileStream("data.txt", FileMode.Open))
{
    // 處理檔案...
} // 編譯器自動產生 try/finally 呼叫 Dispose()

goto 模擬 C++ __leave 行為

try
{
    Console.WriteLine("try 區塊");
    goto EXIT_LABEL; // 跳出 try,直接進入 finally
}
finally
{
    Console.WriteLine("finally 區塊(一定執行)");
}

EXIT_LABEL:
Console.WriteLine("finally 之後的代碼");

拋出例外:throw、重新拋出、包裝例外

拋出新例外

public void SetAge(int age)
{
    if (age < 0 || age > 150)
        throw new ArgumentOutOfRangeException(nameof(age), age, "年齡必須在 0-150 之間");
    
    _age = age;
}

重新拋出原例外(保留堆疊追蹤)

try
{
    riskyOperation();
}
catch (IOException ex)
{
    Log.Error("IO 錯誤", ex);
    throw; // 僅 throw,不加 ex,保留原始堆疊
    // throw ex; // 錯誤!會重設堆疊追蹤至此行
}

包裝例外:提供業務語境(Caller Inform 模式)

try
{
    decimal avg = sum / count;
}
catch (DivideByZeroException ex)
{
    // 包裝新例外,保留內部例外
    throw new InvalidOperationException("計算平均失敗:資料筆數為零", ex);
}

// 捕獲端可透過 InnerException 追溯根因
catch (InvalidOperationException ex) when (ex.InnerException is DivideByZeroException)
{
    // 專門處理分母為零的業務邏輯
}

自訂例外類別設計規範

[Serializable]
public class InsufficientFundsException : Exception
{
    // 業務相關屬性
    public decimal CurrentBalance { get; }
    public decimal RequestedAmount { get; }
    public string AccountId { get; }

    // 三大標準建構子
    public InsufficientFundsException() { }
    
    public InsufficientFundsException(string message) : base(message) { }
    
    public InsufficientFundsException(string message, Exception inner) : base(message, inner) { }

    // 業務資料建構子
    public InsufficientFundsException(string accountId, decimal balance, decimal requested)
        : this($"帳戶 {accountId} 餘額不足:需求 {requested:C},可用 {balance:C}")
    {
        AccountId = accountId;
        CurrentBalance = balance;
        RequestedAmount = requested;
    }

    // 序列化建構子(跨 AppDomain、遠端呼叫必備)
    protected InsufficientFundsException(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
        AccountId = info.GetString(nameof(AccountId));
        CurrentBalance = info.GetDecimal(nameof(CurrentBalance));
        RequestedAmount = info.GetDecimal(nameof(RequestedAmount));
    }

    // 序列化實作
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue(nameof(AccountId), AccountId);
        info.AddValue(nameof(CurrentBalance), CurrentBalance);
        info.AddValue(nameof(RequestedAmount), RequestedAmount);
    }
}

命名與繼承規範

  • 類別名以 Exception 結尾
  • 繼承 System.Exception(不繼承 ApplicationException,已棄用)
  • 標記 [Serializable]
  • 實作 ISerializable(三大建構子 + GetObjectData)
  • 提供業務語境屬性(如帳戶 ID、金額、錯誤代碼)

效能分析:.NET 零成本例外模型

  • 未拋出例外:`try` 區塊幾乎零開銷(無 C++ 風格的物件追蹤)
  • 拋出例外:堆疊追蹤捕獲、例外物件建立、堆疊展開有明顯成本(微秒級)
  • 結論:例外僅用於「真正異常」情況,不可用於正常流程控制(如迴圈結束、驗證失敗)

設計準則總結(Do's and Don'ts)

✅ 應該做❌ 不應該做
提供有意義的錯誤訊息與業務屬性拋出空白或模糊的例外
僅在真正異常情況拋出例外用例外控制正常流程(如 `TryParse` 模式更好)
參數驗證失敗拋 ArgumentException 衍生類拋出 Exception 基底類別
物件狀態不當拋 InvalidOperationException在公開 API 拋出 NullReferenceException、IndexOutOfRangeException
優先使用內建例外類別為每個錯誤都建立自訂例外(過度設計)
使用 throw; 重新拋出保留堆疊使用 throw ex; 丟失原始堆疊
包裝例外時保留 InnerException吞沒例外不記錄、不重拋、不處理
實作 IDisposable / using 管理資源在 `finally` 手動 `Close()` 而不檢查 null

總結

C# 例外處理核心在於:溢位檢查可控(checked/unchecked)、try-catch-finally 結構化處理、正確拋出與重新拋出、自訂例外攜帶業務語境、遵循零成本模型設計原則。掌握這些技術,並避開常見陷阱(吞沒例外、丟失堆疊、濫用例外控制流程),即能建立健壯、可診斷、可維護的錯誤處理體系。

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