CodeGym /課程 /C# SELF /認識例外與它們的階層結構

認識例外與它們的階層結構

C# SELF
等級 13 , 課堂 0
開放

1. 前言

想像你正在寫一個兩數相除的計算機。所有東西都很順,直到有人決定要除以零:

int a = 10;
int b = 0;
int result = a / b; // 砰!
除以零會觸發例外

這時程式就「當掉」了,使用者會看到一個很可怕的錯誤訊息。這就是所謂的例外狀況 —— 一種在「正常」程式流程中不該發生、需要特別處理的事件。

例外 —— 是一種特殊機制,.NET 會用它來告訴你的程式發生了非預期狀況,執行流程必須中斷或特別處理

當執行時發生錯誤(像是除以零、存取不存在的檔案,或是老朋友——解參 null),.NET 會「丟出」(throw)一個特別的例外物件。然後系統會去找有沒有能「接住」(catch)這個狀況的處理器,知道怎麼應對。

為什麼不直接回傳「錯誤碼」?

你當然可以回傳錯誤碼——很多古早語言(Pascal、C,甚至一些現代 API)都這樣做。但這很麻煩又危險:很容易忘記檢查錯誤碼(大家都希望一切順利嘛),而且很難追蹤到底哪裡出錯。例外讓你可以集中追蹤所有錯誤,彈性處理,不用在主程式碼裡塞一堆檢查。

2. 「丟出」例外

例外可以自動發生——像執行時錯誤,也可以用 throw 關鍵字手動丟出:


int[] arr = new int[2];
arr[10] = 5; // 這裡會自動丟出例外 System.IndexOutOfRangeException

throw new Exception("完全災難!"); // 手動丟出 Exception 例外

就算你覺得你的程式設計得很完美,還是可能有意外:使用者關掉檔案、網路斷線、有人把電腦電源拔掉(雖然這個比較少見)。

幸好,.NET 不只給我們「丟」例外的機制,也有捕捉和處理它們的方法——我們等等會講到。

3. 例外的生命週期:從 throwcatch

當程式碼裡發生不妙的事,.NET 會啟動「緊急程序」:

  1. 建立一個例外物件(像 IndexOutOfRangeException)。
  2. 丟出它,用 throw
  3. 尋找處理器:從呼叫堆疊最底層往上找(從目前方法到呼叫它的方法),找第一個型別符合的 catch 區塊。
  4. 如果找到處理器——程式會在 catch 區塊裡繼續執行。
  5. 如果沒找到任何處理器——程式就會崩潰,顯示錯誤細節和呼叫堆疊。

這就像球在樓梯上彈跳——沒遇到接球手前,它會一直「跳」上去。

4. .NET 裡的例外階層

「父子」樹狀結構

在 .NET(還有大多數 OOP 語言)裡,例外是用類別實作的,這些類別會繼承彼此,形成一個階層結構。

它們都有一個共同祖先——基底類別 System.Exception


System.Object
    └─ System.Exception
         ├─ System.SystemException
         │     ├─ System.NullReferenceException
         │     ├─ System.IndexOutOfRangeException
         │     ├─ System.DivideByZeroException
         │     ├─ System.OutOfMemoryException
         │     └─ ... 還有很多其他
         ├─ System.IO.IOException
         │     ├─ System.IO.FileNotFoundException
         │     ├─ System.IO.DirectoryNotFoundException
         │     └─ ...
         ├─ System.ArgumentException
         │     ├─ System.ArgumentNullException
         │     └─ System.ArgumentOutOfRangeException
         └─(你自己寫的 Exception 類別)
.NET 例外階層簡圖

為什麼要用類別?

有了類別階層,你可以一次捕捉一整「類」的問題——比如所有跟函數參數有關的錯誤(ArgumentException 及其子類)。更棒的是,你還可以加自己的錯誤型別(這個之後會講)。

.NET 最常見的例外

  • NullReferenceException —— 嘗試存取 null 物件的方法或屬性。老朋友!
  • DivideByZeroException —— 除以零。
  • IndexOutOfRangeException —— 陣列越界。
  • ArgumentExceptionArgumentNullExceptionArgumentOutOfRangeException —— 方法參數錯誤。
  • FileNotFoundExceptionIOException —— 處理檔案時出問題。
  • InvalidOperationException —— 物件目前狀態下不能做這個操作。
  • FormatException —— 資料格式錯誤(比如想把 "abcd" 轉成數字)。

5. 範例:各種例外是怎麼「爆」的

我們來看幾個會觸發不同例外的程式碼範例。

範例 1:NullReferenceException

string? str = null;
Console.WriteLine(str.Length); // 這裡「砰!」—— str 是 null

輸出:

Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.

範例 2:DivideByZeroException

int a = 42;
int b = 0;
int c = a / b; // 危險!除以 0

輸出:

Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.

範例 3:IndexOutOfRangeException

int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers[5]); // 沒有 index 5 的元素

這裡明顯 index 超過範圍,輸出如下:

Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.

範例 4:FileNotFoundException

using System.IO;

string content = File.ReadAllText("secret.txt");

如果你沒有 "secret.txt" 這個檔案,會看到類似這樣的訊息:

Unhandled exception. System.IO.FileNotFoundException: Could not find file '/Users/zapp/RiderProjects/ConsoleApp1/ConsoleApp1/bin/Debug/net9.0/secret.txt'.

範例 5:FormatException

string input = "十三";
int number = int.Parse(input); // 字串不是數字

輸出:

Unhandled exception. System.FormatException: The input string '十三' was not in a correct format.

6. 怎麼用這些知識?

懂例外怎麼運作,對每個開發者都超重要。這不只是「理論」,而是直接影響你程式穩定性的實戰技能。你知道怎麼正確處理錯誤,你的程式就不會一出錯就「倒」,而是能很友善地告訴使用者哪裡有問題。這讓你的程式更可靠、更專業,也更親民。

而且這不只是方便而已。面試時常常會問你例外處理機制、Exception 類型,還有 .NET 裡這套機制怎麼運作。所以搞懂這個主題,對找工作也很加分。

更何況,你在實際專案裡一定還會遇到例外。處理檔案、網路、資料庫、第三方函式庫和 framework——這些都大量用到例外系統。所以最好一開始就搞懂,之後才不會被嚇到。

7. 處理例外時常見的錯誤

錯誤一:完全忽略例外。
有些新手(有時候連老手也會)覺得例外很煩或礙事。他們要嘛完全不理它,要嘛更糟的是把程式包在 catch { } 裡什麼都不做。結果就是程式「默默」繼續跑,但狀態已經怪怪的,根本找不到問題在哪。

錯誤二:把例外和一般錯誤搞混。
很多人忘了,不是所有錯誤都該用 try-catch 來抓。比如使用者輸入資料格式不對,最好先自己檢查,不要靠 FormatException,更不要把例外當邏輯流程用。例外是給例外狀況,不是日常瑣事。

錯誤三:不懂例外階層。
有時候會去抓太廣的類別(Exception),結果什麼都抓進來,連本來不該處理的錯誤也一起吃掉。有時又抓太窄(IndexOutOfRangeExceptionNullReferenceException),忘了還有其他可能。你要懂 .NET 例外階層怎麼設計,知道你到底想處理什麼。

最重要的是要記得: C# 的例外不是災難,也不是「完蛋了」的訊號,而只是切換到特殊流程的機制。只要用對,它就是很強大的工具。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION