Home [C# 筆記] 使用 throw 來拋出一個例外
Post
Cancel

[C# 筆記] 使用 throw 來拋出一個例外

當我們不想要採用系統預設的例外狀況顯示訊息時,可以透過throw的方式來達到客製化顯示例外訊息的目的。

throw:拋出例外

當你在程式的某處需要引發例外來中斷正常流程時,便可以使用 throw 來拋出一個例外。

1
2
3
4
5
6
7
8
void Print(string name)
{
    if (name == null)
    {
        throw new ArgumentNullException(nameof(name));
    }
    Console.WriteLine(name);
}

Print 方法會先檢查參數 name 是否為 null,如果是的話,便拋出一個 ArgumentNullException 類型的例外,讓呼叫端知道此函式堅決不接受傳入的參數為 null。

第 3~6 行程式碼也可以用一行解決:ArgumentNullException.ThrowIfNull(name);。

順便提及,當你需要拋出 NullReferenceException,除了用 throw new NullReferenceException(),也可以這樣寫:

1
throw null;

再度拋出例外

你也可以在 catch 區塊裡面使用 throw 來將目前的例外再度拋出:

1
2
3
4
5
6
7
8
9
try
{
    DoSomething();
}
catch (Exception ex)
{
    Log(ex); // 把當前的例外資訊寫入 log。
    throw;   // 再次拋出同一個例外。
}

這裡有個細節值得一提:在上述範例中,如果把倒數第二行寫成 throw ex 也可以通過編譯,但其作用稍微不同:

  • 單單寫 throw 會保留原本的堆疊追蹤資訊(stack trace)。也就是說,透過堆疊追蹤資訊,便能抓到原始的例外。
  • 如果寫 throw ex,則會重設堆疊追蹤資訊,這將導致呼叫端無法透過例外物件的 StackTrace 屬性得知引發例外的源頭是哪一行程式碼。基於此緣故,通常不建議採用此寫法。

呼叫堆疊與堆疊追蹤

堆疊追蹤(stack trace)是一個字串,裡面包含了當前呼叫堆疊(call stack)裡面的所有方法的名稱,以及方法所在的程式碼行號——如果編譯時有啟用除錯資訊的話。
呼叫堆疊指的是一個記憶體區塊,其中保存了某一條執行緒當時的所有方法呼叫的資訊,例如傳入方法的參數、方法的區域變數等等。

於 catch 區塊中再次拋出例外時,你也可以拋出一個新的、不同類型的例外:

1
2
3
4
5
6
7
8
9
10
11
DateTime StringToDate(string input)
{
    try
    {
        return Convert.ToDateTime(input);
    }
    catch (FormatException ex)
    {
        throw new ArgumentException($"無效的引數: {nameof(input)}", ex);
    }
}

請注意上述範例在拋出一個新的 ArgumentException 時,有把當前的例外 ex 傳入建構子的第二個參數,這會把當前的例外保存於新例外的 InnerException 屬性。也就是說,在拋出新例外的同時,依然保存原始的例外資訊(也許呼叫端在診斷錯誤的時候會用到)。

一般來說,以下幾種場合會在 catch 區塊中拋出不同的例外:

  • 在處理當前的例外時,又發生了其他意外狀況。
  • 讓外層接收到更一般的例外類型,以便隱藏底層的細節(不想讓外界知道太多、避免駭客攻擊)。
  • 讓外層接收到更特殊的例外類型,以便呼叫端更明確知道發生錯誤的原因。

C# 例外處理(Exception Handling) by huanlintalk

This post is licensed under CC BY 4.0 by the author.