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
{
// 可能拋出例外的代碼
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("清理資源、關閉連線...");
}
由衍生類別到基底類別。反向會導致編譯錯誤 CS0160:「先前的 catch 子句已捕獲所有此類或超類型的例外」。
void Level1() { Level2(); }
void Level2() { Level3(); }
void Level3() { throw new InvalidOperationException("底層錯誤"); }
try { Level1(); }
catch (InvalidOperationException ex)
{
// 此處捕獲,StackTrace 保留完整呼叫鏈
Console.WriteLine(ex.StackTrace);
}
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()
try
{
Console.WriteLine("try 區塊");
goto EXIT_LABEL; // 跳出 try,直接進入 finally
}
finally
{
Console.WriteLine("finally 區塊(一定執行)");
}
EXIT_LABEL:
Console.WriteLine("finally 之後的代碼");
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; // 錯誤!會重設堆疊追蹤至此行
}
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)| ✅ 應該做 | ❌ 不應該做 |
|---|---|
| 提供有意義的錯誤訊息與業務屬性 | 拋出空白或模糊的例外 |
| 僅在真正異常情況拋出例外 | 用例外控制正常流程(如 `TryParse` 模式更好) |
參數驗證失敗拋 ArgumentException 衍生類 | 拋出 Exception 基底類別 |
物件狀態不當拋 InvalidOperationException | 在公開 API 拋出 NullReferenceException、IndexOutOfRangeException |
| 優先使用內建例外類別 | 為每個錯誤都建立自訂例外(過度設計) |
使用 throw; 重新拋出保留堆疊 | 使用 throw ex; 丟失原始堆疊 |
包裝例外時保留 InnerException | 吞沒例外不記錄、不重拋、不處理 |
實作 IDisposable / using 管理資源 | 在 `finally` 手動 `Close()` 而不檢查 null |
C# 例外處理核心在於:溢位檢查可控(checked/unchecked)、try-catch-finally 結構化處理、正確拋出與重新拋出、自訂例外攜帶業務語境、遵循零成本模型設計原則。掌握這些技術,並避開常見陷阱(吞沒例外、丟失堆疊、濫用例外控制流程),即能建立健壯、可診斷、可維護的錯誤處理體系。