☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
C# 基礎 中級

⚠️ 例外處理與除錯

📌 什麼是例外(Exception)?

例外就像開車時遇到的「路障」。如果你沒有準備好應對方案,程式就會直接撞上去然後崩潰。例外處理就是幫你設計繞路方案。


📌 try / catch / finally

try // 嘗試執行可能出錯的程式碼(像是試著過馬路)
{
    // 嘗試把字串轉成數字
    string input = "abc";
    int number = int.Parse(input); // 💥 這行會出錯,因為 "abc" 不是數字
    Console.WriteLine(number); // 這行不會執行
}
catch (FormatException ex) // 捕捉「格式錯誤」的例外
{
    // 印出錯誤訊息,告訴使用者輸入格式不對
    Console.WriteLine($"格式錯誤:{ex.Message}");
}
catch (OverflowException ex) // 捕捉「數字溢位」的例外
{
    // 數字太大或太小時會觸發
    Console.WriteLine($"數字超出範圍:{ex.Message}");
}
finally // 不管有沒有出錯,這裡的程式碼都會執行
{
    // 通常用來釋放資源(關閉檔案、資料庫連線等)
    Console.WriteLine("不管成功失敗,我都會執行");
}

📌 多重 catch 與 when 子句

try // 嘗試執行程式碼
{
    // 模擬根據錯誤碼拋出不同例外
    int errorCode = 404;
    throw new HttpRequestException($"HTTP 錯誤:{errorCode}"); // 手動拋出例外
}
catch (HttpRequestException ex) when (ex.Message.Contains("404")) // 只捕捉包含 404 的例外
{
    // 處理找不到頁面的情況
    Console.WriteLine("頁面不存在(404)");
}
catch (HttpRequestException ex) when (ex.Message.Contains("500")) // 只捕捉包含 500 的例外
{
    // 處理伺服器錯誤的情況
    Console.WriteLine("伺服器錯誤(500)");
}
catch (Exception ex) // 捕捉所有其他例外(最通用的放最後)
{
    // 處理未預期的錯誤
    Console.WriteLine($"未預期的錯誤:{ex.Message}");
}

重點: when 子句讓你可以對同一種例外類型做更細緻的分類處理,就像醫院的分級診療。


📌 Exception 階層架構

// Exception 的繼承關係(像家族樹)
// Exception                         ← 所有例外的祖先
//   ├── SystemException             ← 系統層級例外
//   │     ├── NullReferenceException  ← 物件為 null 時存取其成員
//   │     ├── IndexOutOfRangeException ← 陣列索引超出範圍
//   │     ├── InvalidOperationException ← 操作在目前狀態下無效
//   │     └── ArgumentException       ← 傳入的參數不合法
//   │           └── ArgumentNullException ← 參數為 null
//   └── ApplicationException        ← 應用程式層級例外(較少用)

📌 自訂例外

// 建立自訂例外類別(繼承 Exception)
public class InsufficientBalanceException : Exception // 餘額不足例外
{
    // 目前餘額
    public decimal CurrentBalance { get; }

    // 嘗試提領的金額
    public decimal WithdrawAmount { get; }

    // 建構函式,接收餘額和提領金額
    public InsufficientBalanceException(decimal balance, decimal amount)
        : base($"餘額不足!目前餘額:{balance},嘗試提領:{amount}") // 呼叫父類建構函式設定訊息
    {
        CurrentBalance = balance;  // 儲存目前餘額
        WithdrawAmount = amount;   // 儲存提領金額
    }
}

// 使用自訂例外
public class BankAccount // 銀行帳戶類別
{
    // 帳戶餘額
    public decimal Balance { get; private set; } = 1000m;

    // 提款方法
    public void Withdraw(decimal amount)
    {
        if (amount > Balance) // 如果提領金額超過餘額
        {
            // 拋出自訂例外,附帶餘額和金額資訊
            throw new InsufficientBalanceException(Balance, amount);
        }
        Balance -= amount; // 扣除餘額
    }
}

📌 throw vs throw ex 的差異

// ❌ 使用 throw ex — 會遺失原始堆疊追蹤
try
{
    // 呼叫某個可能出錯的方法
    SomeMethod();
}
catch (Exception ex) // 捕捉到例外
{
    // 記錄錯誤日誌
    Console.WriteLine(ex.Message);
    throw ex; // ❌ 堆疊追蹤會從這裡重新開始,原始錯誤位置遺失
}

// ✅ 使用 throw — 保留完整的堆疊追蹤
try
{
    // 呼叫某個可能出錯的方法
    SomeMethod();
}
catch (Exception ex) // 捕捉到例外
{
    // 記錄錯誤日誌
    Console.WriteLine(ex.Message);
    throw; // ✅ 保留原始堆疊追蹤,可以追溯到真正出錯的地方
}

比喻: throw ex 就像把犯罪現場的指紋擦掉再報警,警察就找不到原始線索了。throw 則是完整保留犯罪現場。


📌 除錯技巧

中斷點(Breakpoint)

在 Visual Studio 中,點擊程式碼左邊的灰色區域即可設定中斷點。程式執行到該行時會暫停,讓你檢查變數的值。

監看式(Watch)

在中斷點暫停時,可以把變數加入「監看式」視窗,即時觀察變數的變化。

即時運算視窗(Immediate Window)

在除錯暫停時,可以在即時運算視窗輸入 C# 運算式來測試:

// 在 Immediate Window 中可以這樣輸入:
// ? myVariable          ← 查看變數的值
// ? myList.Count        ← 查看集合的元素數量
// ? myObject.ToString() ← 呼叫物件的方法
// myVariable = 42       ← 即時修改變數的值(測試用)

🤔 我這樣寫為什麼會錯?

❌ 錯誤 1:捕捉範圍太廣

// ❌ 錯誤寫法:捕捉所有 Exception,吞掉所有錯誤
try
{
    // 執行某個操作
    ProcessData();
}
catch (Exception) // 捕捉所有例外,但什麼都不做
{
    // 空的 catch 區塊!錯誤被默默吞掉,你永遠不知道出了什麼問題
}
// ✅ 正確寫法:捕捉具體的例外,並適當處理
try
{
    // 執行某個操作
    ProcessData();
}
catch (FileNotFoundException ex) // 只捕捉檔案不存在的例外
{
    // 記錄錯誤並通知使用者
    Console.WriteLine($"找不到檔案:{ex.FileName}");
}
catch (Exception ex) // 其他未預期的例外
{
    // 至少要記錄錯誤日誌
    Console.WriteLine($"未預期錯誤:{ex}");
    throw; // 重新拋出,讓上層處理
}

解釋: 空的 catch 區塊就像把所有警報都關掉,表面上一切正常,但問題其實在暗處惡化。

❌ 錯誤 2:throw new Exception() 包裝錯誤

// ❌ 錯誤寫法:用新的 Exception 包裝,遺失原始資訊
try
{
    // 執行可能出錯的操作
    ConnectToDatabase();
}
catch (Exception ex) // 捕捉到例外
{
    // 建立全新的例外,原始的例外類型和堆疊追蹤全部遺失
    throw new Exception("連線失敗"); // ❌ 遺失原始例外的所有資訊
}
// ✅ 正確寫法:用 inner exception 保留原始例外
try
{
    // 執行可能出錯的操作
    ConnectToDatabase();
}
catch (Exception ex) // 捕捉到例外
{
    // 建立新例外時,把原始例外作為 inner exception 傳入
    throw new InvalidOperationException("資料庫連線失敗", ex); // ✅ 保留原始例外
}

解釋: 就像轉述別人的話時,不只說結論,也要說明原始來源,這樣才能追溯問題根源。

❌ 錯誤 3:在 finally 中 return

// ❌ 錯誤寫法:在 finally 中使用 return
static int GetValue()
{
    try
    {
        return 1; // 嘗試回傳 1
    }
    finally
    {
        return 2; // ❌ finally 中的 return 會覆蓋 try 中的 return(C# 不允許此寫法)
    }
}
// ✅ 正確寫法:finally 只做清理工作
static int GetValue()
{
    int result = 0; // 宣告結果變數
    try
    {
        result = 1; // 設定結果
        return result; // 回傳結果
    }
    finally
    {
        // finally 只做清理,不要 return
        Console.WriteLine("清理完成"); // 釋放資源等操作
    }
}

解釋: finally 的職責是清理資源(像是打掃戰場),不應該用來改變程式的回傳值。

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊