C# 使用 try、catch、finally 三大關鍵字構建結構化異常處理(SEH):
static double SafeDivision(double x, double y)
{
if (y == 0)
throw new DivideByZeroException("除數不能為零");
return x / y;
}
static void Main()
{
double a = 98, b = 0, result = 0;
try
{
result = SafeDivision(a, b);
Console.WriteLine($"{a} / {b} = {result}");
}
catch (DivideByZeroException ex)
{
Console.WriteLine("嘗試除以零:" + ex.Message);
}
finally
{
Console.WriteLine("運算結束"); // 永遠執行
}
}
當方法拋出異常且未在當前 try 區塊捕獲時,CLR 會展開呼叫堆疊,尋找最近的匹配 catch:
void Level1() { Level2(); }
void Level2() { Level3(); }
void Level3() { throw new InvalidOperationException(); }
try { Level1(); }
catch (InvalidOperationException) { /* 這裡捕獲 */ }
若堆疊頂端仍無匹配 catch,進程終止並顯示未處理異常對話框。
[Serializable]
public class BusinessRuleException : Exception
{
public string RuleCode { get; }
public object[] Parameters { get; }
public BusinessRuleException(string ruleCode, string message, params object[] parameters)
: base(message)
{
RuleCode = ruleCode;
Parameters = parameters;
}
// 序列化建構函式(跨 AppDomain/遠端調用必備)
protected BusinessRuleException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
RuleCode = info.GetString(nameof(RuleCode));
Parameters = (object[])info.GetValue(nameof(Parameters), typeof(object[]));
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue(nameof(RuleCode), RuleCode);
info.AddValue(nameof(Parameters), Parameters);
}
}
// 使用
throw new BusinessRuleException("AGE_001", "年齡必須大於 18 歲,實際值:{0}", 15);
try
{
using var sw = new StreamWriter(@"C:\test\test.txt");
sw.WriteLine("Hello");
}
catch (FileNotFoundException ex) // 最具體
{
Console.WriteLine("檔案不存在:" + ex.FileName);
}
catch (IOException ex) // 較一般
{
Console.WriteLine("IO 錯誤:" + ex.Message);
}
catch (Exception ex) // 最一般(最後兜底)
{
Console.WriteLine("未預期錯誤:" + ex);
throw; // 重新拋出,保留堆疊追蹤
}
finally
{
// 清理資源(using 已自動處理 StreamWriter)
}
FileStream fs = null;
try
{
fs = new FileInfo(@"C:\file.txt").OpenWrite();
fs.WriteByte(0xF);
}
finally
{
// 即使 try 區塊拋出異常,finally 仍執行
if (fs != null) fs.Close(); // 釋放檔案鎖定
}
// 現代寫法:using 陳述式(編譯器轉換為 try-finally)
using var fs = new FileInfo(@"C:\file.txt").OpenWrite();
fs.WriteByte(0xF);
// 隱含 finally: fs.Dispose()
| 原則 | 說明 |
|---|---|
| 僅捕獲可處理的異常 | 捕獲 Exception 後必須 throw 重拋,否則吞沒錯誤 |
| 避免控制流程使用異常 | 異常成本高,業務邏輯用 TryParse、TryGetValue、Result<T> 模式 |
| 保留堆疊追蹤 | 用 throw; 而非 throw ex; |
| 提供豐富錯誤資訊 | 自訂異常攜帶錯誤碼、參數、關聯物件 |
| 非同步正確處理 | async Task 方法異常在 await 時拋出,async void 僅限事件處理 |
| 全域處理 | ASP.NET Core: UseExceptionHandler、WPF: DispatcherUnhandledException |
TypeInitializationException,原始異常在 InnerExceptionTaskScheduler.UnobservedTaskException 觀測OutOfMemoryException 通常不可恢復,僅能記錄並優雅退出public readonly struct Result<T>
{
public T Value { get; }
public Exception Error { get; }
public bool IsSuccess => Error == null;
private Result(T value, Exception error) { Value = value; Error = error; }
public static Result<T> Success(T value) => new(value, null);
public static Result<T> Failure(Exception error) => new(default, error);
}
// 使用
Result<int> ParseInt(string s)
=> int.TryParse(s, out var v) ? Result<int>.Success(v)
: Result<int>.Failure(new FormatException($"非整數:{s}"));