在 C# 程式開發中,異常處理是確保應用程式穩健性的關鍵機制。正確地建立、擲回與處理異常,不僅能讓程式在發生錯誤時優雅地恢復,更能提供清晰的診斷資訊,協助開發者快速定位問題。本文將深入探討異常的生命週期、何時該擲回異常、自訂異常類別的設計原則,以及 try/catch/finally 區塊的進階用法。
異常代表執行期間發生的錯誤狀況。當方法無法完成其既定功能時,應建立描述錯誤的異常物件,並透過 throw 關鍵字擲回。執行階段會沿著呼叫堆疊尋找相容的異常處理常式。
最常見的情況是參數驗證失敗。例如傳入 null 參數時,應擲回 ArgumentNullException 或 ArgumentException:
static void CopyObject(SampleClass original)
{
if (original == null)
{
throw new ArgumentException("參數不可為 null", nameof(original));
}
// 正常邏輯...
}
當物件處於無法執行特定作業的狀態(如嘗試寫入唯讀檔案),應擲回 InvalidOperationException 或其衍生類別:
class ProgramLog
{
FileStream logFile = null;
void WriteLog()
{
if (!logFile.CanWrite)
{
throw new InvalidOperationException("記錄檔不可為唯讀");
}
// 寫入邏輯...
}
}
若方法參數觸發了底層異常(如索引越界),應捕捉原始異常並包裝為 ArgumentException,同時透過 InnerException 保留原始錯誤資訊:
static int GetValueFromArray(int[] array, int index)
{
try
{
return array[index];
}
catch (IndexOutOfRangeException ex)
{
throw new ArgumentException("索引超出範圍", nameof(index), ex);
}
}
throw 點開始的呼叫堆疊,包含方法名稱、檔案名與行號。ArgumentException 專屬):導致異常的參數名稱,屬性設定子應設為 value。System.Exception、System.SystemException、NullReferenceException 或 IndexOutOfRangeException。Debug.Assert。雖然 .NET 提供豐富的內建異常,但特定業務領域常需要自訂異常以精確表達錯誤語意。自訂異常類別應繼承 Exception,並至少實作四個建構函式:
[Serializable]
public class InvalidDepartmentException : Exception
{
public InvalidDepartmentException() : base() { }
public InvalidDepartmentException(string message) : base(message) { }
public InvalidDepartmentException(string message, Exception inner) : base(message, inner) { }
// 序列化建構函式(跨應用程式域傳遞時必要)
protected InvalidDepartmentException(SerializationInfo info, StreamingContext context)
: base(info, context) { }
}
僅在新增屬性能協助解決異常時才加入屬性,並覆寫 ToString() 包含額外資訊。
C# 使用 try 區塊包裹可能擲回異常的程式碼,搭配 catch 處理異常,finally 則確保清理程式碼一定執行。
// 僅 catch
try { /* 可能擲回異常的程式碼 */ }
catch (SpecificException ex) { /* 處理 */ }
// 僅 finally
try { /* 程式碼 */ }
finally { /* 清理資源 */ }
// 完整形式
try { /* 程式碼 */ }
catch (SpecificException ex) { /* 處理 */ }
finally { /* 無論是否發生異常皆執行 */ }
System.Exception 且不重新擲回。catch 由上而下評估,最符合的衍生類別優先,故具體類別應放在前面。FileNotFoundException 後提示使用者重新輸入檔名。throw; 保留原始堆疊。try
{
// 存取資源
}
catch (UnauthorizedAccessException e)
{
LogError(e); // 記錄日誌
throw; // 重新擲回原始異常,保留堆疊追蹤
}
finally 區塊會在 try 與 catch 完成後執行,無論是否發生異常。適用於釋放非受控資源(檔案流、資料庫連線、圖形控制代碼等),不必等待記憶體回收。
FileStream file = null;
FileInfo fileInfo = new FileInfo(@"C:\file.txt");
try
{
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
if (file != null) // OpenWrite 可能失敗,檢查 null 避免 NullReferenceException
{
file.Close();
}
}
同樣適用於資料庫連線關閉,避免連線池耗盡。
掌握異常的建立、擲回與處理,是撰寫高品質 C# 程式的核心能力。遵循「具體異常、保留堆疊、finally 清理資源」三大原則,能讓您的應用程式在面對錯誤時既穩健又易於維護。