throw 基本用法
// 1. 拋出新例外
throw new ArgumentNullException(nameof(id), "ID 不可為空");
// 2. 重新拋出目前捕獲的例外 (保留堆疊追蹤)
try
{
RiskyOperation();
}
catch (Exception ex)
{
LogError(ex);
throw; // 關鍵:不是 throw ex!
}
// 3. 捕獲後包裝為新例外 (保留 InnerException)
catch (SqlException ex)
{
throw new DataAccessException("資料庫操作失敗", ex);
}
throw vs throw ex 差異示範
void MethodA()
{
try { MethodB(); }
catch (Exception ex)
{
Console.WriteLine("MethodA catch 堆疊:");
Console.WriteLine(ex.StackTrace);
throw; // 正確:保留 MethodB 完整堆疊
}
}
void MethodB()
{
try { MethodC(); }
catch (Exception ex)
{
Console.WriteLine("MethodB catch 堆疊:");
Console.WriteLine(ex.StackTrace);
throw ex; // 錯誤:堆疊追蹤從這裡重設,MethodC 資訊遺失
}
}
void MethodC() => throw new InvalidOperationException("原始錯誤");
完整自訂例外範本
using System;
using System.Runtime.Serialization;
[Serializable]
public class UserEmployeeException : Exception
{
public string ErrorCode { get; }
public string UserId { get; }
// 無參數
public UserEmployeeException() : base("使用者員工操作異常") { }
// 訊息
public UserEmployeeException(string message) : base(message) { }
// 訊息 + 內部例外
public UserEmployeeException(string message, Exception inner)
: base(message, inner) { }
// 豐富建構函式
public UserEmployeeException(string errorCode, string userId, string message)
: base(message)
{
ErrorCode = errorCode;
UserId = userId;
}
// 序列化建構函式 (跨 AppDomain/遠端必須)
protected UserEmployeeException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
ErrorCode = info.GetString(nameof(ErrorCode));
UserId = info.GetString(nameof(UserId));
}
// 序列化資料輸出
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue(nameof(ErrorCode), ErrorCode);
info.AddValue(nameof(UserId), UserId);
}
}
使用範例
class Program
{
static void Main()
{
try
{
ProcessEmployee("EMP001");
}
catch (UserEmployeeException ex)
{
Console.WriteLine($"錯誤代碼:{ex.ErrorCode}");
Console.WriteLine($"使用者:{ex.UserId}");
Console.WriteLine($"訊息:{ex.Message}");
if (ex.InnerException != null)
Console.WriteLine($"內部:{ex.InnerException.Message}");
}
}
static void ProcessEmployee(string id)
{
if (string.IsNullOrEmpty(id))
throw new UserEmployeeException("E001", "SYSTEM", "員工 ID 不可為空");
if (!EmployeeExists(id))
throw new UserEmployeeException("E002", id, "員工不存在");
}
static bool EmployeeExists(string id) => false;
}
執行輸出
錯誤代碼:E001
使用者:SYSTEM
訊息:員工 ID 不可為空
設計原則總結
- 類別命名以 Exception 結尾:
UserEmployeeException
- 標記 [Serializable]:支援跨應用程式域、遠端呼叫、記錄序列化
- 提供四大標準建構函式:無參數、訊息、訊息+內部例外、序列化
- 豐富建構函式攜帶業務資料:錯誤代碼、實體 ID、驗證失敗欄位等
- 實作 GetObjectData / 序列化建構函式:確保自訂屬性能正確序列化
- 重新拋出永遠用 throw;:保留完整堆疊追蹤
- 包裝例外時傳入 innerException:建立因果鏈
進階:例外篩選器搭配自訂例外 (C# 6+)
try
{
ProcessData();
}
catch (UserEmployeeException ex) when (ex.ErrorCode.StartsWith("E00"))
{
// 僅處理驗證類錯誤
ShowValidationError(ex);
}
catch (UserEmployeeException ex) when (ex.ErrorCode.StartsWith("E01"))
{
// 僅處理資料庫類錯誤
ShowDbError(ex);
}