CodeGym /課程 /C# SELF /C# 的多重 catch 和過濾器

C# 的多重 catch 和過濾器

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

1. 前言

當你的程式比 Hello, world! 再複雜一點時,你很快就會遇到一個問題:要針對不同型別的錯誤做不同處理。想像一下:你在處理檔案、網路、資料庫——每種錯誤情境都該有不同反應。比如檔案不存在時可以讓使用者選別的檔案,網路錯誤時可以重試或顯示個鼓勵訊息「檢查一下網路線,也許貓又咬斷了」。

在 C# 裡,這就用多個 catch 區塊連著寫。每個區塊專門處理一種例外型別(還有它的子類)。

多重 catch 的結構


try
{
    // 這裡是可能丟出各種例外的程式碼
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"檔案沒找到: {ex.Message}");
}
catch (IOException ex) // 捕捉所有輸入/輸出錯誤,除了 FileNotFoundException
{
    Console.WriteLine($"輸入輸出錯誤: {ex.Message}");
}
catch (Exception ex)
{
    Console.WriteLine($"有什麼地方怪怪的: {ex.Message}");
}
多個 catch 區塊處理不同型別的錯誤

重點! 編譯器會從上往下找,遇到第一個型別符合的區塊就進去。如果抓到 FileNotFoundException,後面的區塊就不會再進去了。

「鏈條」示意圖

例外型別 會被哪個區塊抓到?
FileNotFoundException 第一個 catch
IOException(其他) 第二個 catch
ArgumentException 第三個(通用) catch

為什麼不能混著來?
最「廣」的陷阱——像 catch (Exception)——一定要放在最後面,不然它會「吃掉」所有例外,專門的 catch 就永遠不會被執行到。

這就像整棟大樓的火警警報器都關掉,只因為有一個被烤麵包機觸發了:這樣後面真的失火系統就完全沒反應了。

實戰範例

using System;
using System.IO;

class Program
{
    static void Main()
    {
        try
        {
            double result = CalculateAverageAgeFromFile("users.txt");
            Console.WriteLine($"平均年齡 — {result}");
        }
        catch (FileNotFoundException ex)
        {
            Console.WriteLine("錯誤:檔案沒找到。請檢查路徑。");
        }
        catch (FormatException ex)
        {
            Console.WriteLine("資料錯誤:年齡讀不出來。");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"其他錯誤: {ex.Message}");
        }
    }

    static double CalculateAverageAgeFromFile(string filePath)
    {
        // (實作:讀檔案、解析年齡、計算平均)
        // ...
        throw new NotImplementedException();
    }
}

這裡我們明確分開:如果檔案不存在(FileNotFoundException)要怎麼辦,如果檔案裡資料「怪怪的」(FormatException)又怎麼辦。其他情況就交給第三個「備用」區塊。

2. catch 過濾器:抓細節

多個 catch 區塊很方便,但有時還不夠。有時候同一種例外裡,根據情境你想要不同反應。

比如網路不通,如果是沒網路流量——可以試著重連。如果是伺服器沒回應——也許該顯示別的訊息。

這時就輪到 catch 過濾器 上場了——C# 的超強功能,可以不只根據型別,還能加條件來抓例外。

when 過濾器語法


catch (IOException ex) when (ex.Message.Contains("沒有磁碟"))
{
    Console.WriteLine("哎呀!看來你把隨身碟拔掉了。");
}
catch (IOException ex)
{
    Console.WriteLine("其他輸入/輸出錯誤: " + ex.Message);
}
catch when 過濾器細緻條件攔截

這裡第一個區塊只抓 IOException,而且訊息裡要有 "沒有磁碟" 這句話——其他的就進第二個區塊。

實際應用

過濾器特別適合處理網路錯誤,根據例外的內部屬性決定要不要重試,還是直接報錯。

再舉個例子:假設我們有個方法在解析檔案時,不只要抓「生氣」的 FormatException,還要特別處理年齡欄位(比如年齡寫成 "abc" 不是數字)。

catch (FormatException ex) when (ex.Message.Contains("年齡"))
{
    Console.WriteLine("錯誤:年齡讀不出來。請檢查資料。");
}
catch (FormatException ex)
{
    Console.WriteLine("資料格式錯誤: " + ex.Message);
}

過濾器與效能

過濾器很方便,但要記得,過濾條件是在 進入 catch 區塊前就先算好,如果條件不成立,區塊內容根本不會執行。

還有,如果過濾器本身丟出例外(比如 when 裡面自己除以零),這個例外 catch 永遠 不會抓到。要小心這點。

3. 多重 catch + 過濾器一起用

想像你的專案不只要根據例外型別,還要看它的內部狀態。比如處理 IOException 時,遇到檔案系統權限問題或磁碟空間不足要有不同反應。


try
{
    File.AppendAllText("log.txt", "新紀錄\n");
}
catch (IOException ex) when (ex.Message.Contains("沒有空間"))
{
    Console.WriteLine("錯誤:磁碟空間用完了!");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("錯誤:沒有寫入檔案的權限。請用管理員身份執行。");
}
catch (IOException ex)
{
    Console.WriteLine("其他磁碟錯誤: " + ex.Message);
}
過濾器和不同型別 catch 的組合

這裡我們同時用過濾器和型別分流。這種彈性在開發複雜應用時特別有用,可以針對常見錯誤給出明確回應。

4. 過濾器和多重 catch 的細節與陷阱

  • catch 過濾器可以用例外變數(ex),但不能改它。
  • 如果你在 when 裡丟出例外,這個 catch 區塊完全不會抓到,會直接往上丟,就像沒過濾器一樣。
  • 過濾器邏輯變成合約的一部分:你的同事要知道,不是每個 IOException 都會被抓到——只有條件成立才會。
  • 如果你整個方法只用一個 catch,裡面加過濾器,有可能「多餘」的例外根本沒被抓到。所以有疑慮時,還是用明確分開的區塊比較保險。
  • 大型應用可以用過濾器來集中處理,比如只記錄嚴重錯誤。

5. 面試和實戰場景

懂得用過濾器和多重 catch 不只是為了寫漂亮或 clean code 分數高。這是真本事,面試時尤其是要維護大型、複雜、分散式應用的職位,常常會考。

常見面試題:

  • 在 C# 裡怎麼處理檔案讀取的不同錯誤情境,讓「沒檔案」、「磁碟被佔用」、「資料壞掉」都能顯示不同訊息?
  • 如果你加了一個通用 catch (Exception),怎麼避免「吃掉」重要錯誤?
  • catch 過濾器有什麼用?裡面可以 throw 嗎?
2
任務
C# SELF, 等級 13, 課堂 4
上鎖
使用多個 `catch` 來處理例外狀況
使用多個 `catch` 來處理例外狀況
2
任務
C# SELF, 等級 13, 課堂 4
上鎖
使用 `catch` 過濾器
使用 `catch` 過濾器
1
問卷/小測驗
認識例外處理,等級 13,課堂 4
未開放
認識例外處理
呼叫堆疊跟自訂例外的建立
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION