第 9 課 · 約 12 分鐘 · ← 第 8 課

異常:執行時出問題怎麼辦

編譯錯誤在釋出前攔你;異常是執行中才冒頭的問題——壞輸入、不存在的鍵、除以零。C# 的答案:try 包住有風險的部分,catch 接住失敗,finally 負責清理。

try / catch:把爆炸圈住

string[] inputs = { "42", "abc" };

foreach (string s in inputs)
{
    try
    {
        Console.WriteLine("parsed: " + int.Parse(s));
    }
    catch (FormatException)              // 按型別接——只接這一種失敗
    {
        Console.WriteLine("not a number: " + s);
    }
}

沒有 try/catch,第二個 int.Parse 會殺掉整個程式。有了它,失敗被圈進一輪迴圈,程式繼續跑。儘量接最具體的型別——catch (Exception) 什麼都能接,也常常把 bug 一起藏了。

異常物件:出了什麼事,白紙黑字

int Divide(int a, int b)
{
    if (b == 0)
    {
        throw new Exception("division by zero is undefined");   // 自己拋
    }
    return a / b;
}

try
{
    Console.WriteLine(Divide(10, 2));
    Console.WriteLine(Divide(1, 0));
}
catch (Exception e)                    // e 就是那個異常——讀它的 Message
{
    Console.WriteLine("caught: " + e.Message);
}
finally
{
    Console.WriteLine("always runs");  // 成功失敗都跑——清理程式碼放這
}

finally 無論如何都執行:成功、被接住的失敗、沒人接的異常。關檔案、關連線的程式碼住這裡——引擎裡沒有檔案,但形狀是通用的。

型別是一個家族——接你想接的那支

Exception                      // 根——所有異常都是它
└── SystemException
    ├── FormatException             // int.Parse("abc")
    ├── NullReferenceException      // 用了 null 物件
    ├── IndexOutOfRangeException    // 陣列下標越界
    ├── DivideByZeroException       // 整數除以零
    └── ArgumentException           // 你傳了無效的東西
        └── ArgumentNullException   // ……具體地:傳了 null

接住父類等於接住它所有子類:catch (Exception e) 全接;catch (SystemException e) 接系統家族。多個 catch 塊按最具體在前排序——第一個命中者贏。

能避免的就別 try/catch

地道的分工:異常留給異常性的問題,TryParse 留給預期內的失敗:

string[] inputs = { "42", "xyz" };

foreach (string s in inputs)
{
    if (int.TryParse(s, out int n))     // 失敗是正常分支
    {
        Console.WriteLine("got " + n);
    }
    else
    {
        Console.WriteLine("ignored " + s);
    }
}

經驗法則:"失敗"是你邏輯裡的正常分支(使用者輸入!)就用 Try 系方法;失敗意味著真的出了毛病,讓異常飛,在能真正處理它的那一層接住。

拋自己的異常

當你的方法收到了無法工作的引數,C# 慣例是拋 ArgumentException(或其更具體的子類),訊息說清哪裡不對:

void SetAge(int age)
{
    if (age < 0)
    {
        throw new ArgumentException("age cannot be negative: " + age);
    }
    // ……存起來
}

早拋,訊息寫清楚——另一個選項(默默存下胡說八道)會變成別人凌晨兩點的除錯現場。

現在就練