try-catch (C# 參考)

try-catch 陳述式包含 try 區塊後面接著一個或多個 catch 子句,指定不同例外狀況的處理常式。

擲回例外狀況時,Common Language Runtime (CLR) 會尋找處理此例外狀況的 catch 陳述式。 如果目前執行的方法不包含這類 catch 區塊,CLR 會在呼叫堆疊上查看呼叫目前方法的方法,依此類推。 如果找不到 catch 區塊,則 CLR 會向使用者顯示未處理的例外狀況訊息,並停止執行程式。

try 區塊包含可能會造成例外狀況的防護程式碼。 區塊會執行直到例外狀況擲回,或已成功完成。 例如,以下的轉換 null 物件嘗試會引發 NullReferenceException 例外狀況:

object o2 = null;
try
{
    int i2 = (int)o2;   // Error
}

雖然可以不含引數使用 catch 子句,來攔截任何類型的例外狀況,不建議使用這種用法。 一般而言,您應該只攔截那些您知道如何從中復原的例外狀況。 因此,您應該永遠指定衍生自 System.Exception 的物件引數,例如:

catch (InvalidCastException e)
{
}

您可以在相同 try catch 陳述式中的子句中使用多個特定的 catch。 在此情況下,catch 子句的順序很重要,因為會依順序檢查 catch 子句。 在較不特定的例外狀況之前攔截較特定的例外狀況。 如果您排序 catch 區塊,使得永遠不會達到較新的區塊,編譯器會產生錯誤。

使用 catch 引數是篩選您想要處理的例外狀況的一種方式。 您也可以使用例外狀況篩選,進一步檢查例外狀況來決定是否要處理。 如果例外狀況篩選會傳回 false,則搜尋處理常式會繼續。

catch (ArgumentException e) when (e.ParamName == "…")
{
}

例外狀況篩選條件優於攔截和重新擲回 (說明如下所示),因為篩選條件不會損壞堆疊。 如果之後的處理常式傾印堆疊,您可以看到例外狀況原本來自何處,而不是只重新擲回的最後一個位置。 例外狀況篩選條件運算式的常見用法是記錄。 您可以建立一律會傳回 false 同時會輸出到記錄檔的篩選,您可以持續記錄例外狀況,而不必處理它們並重新擲回。

throw 陳述式可以在 catch 區塊中用來重新擲回 catch 陳述式攔截到的例外狀況。 下列範例會從 IOException 例外狀況擷取來源資訊,然後將例外狀況擲回父方法。

catch (FileNotFoundException e)
{
    // FileNotFoundExceptions are handled here.
}
catch (IOException e)
{
    // Extract some information from this exception, and then
    // throw it to the parent method.
    if (e.Source != null)
        Console.WriteLine("IOException source: {0}", e.Source);
    throw;
}

您可以攔截一個例外狀況,並擲回不同的例外狀況。 執行此動作時,請將您攔截的例外狀況指定為內部例外狀況,如下列範例所示。

catch (InvalidCastException e)
{
    // Perform some action here, and then throw a new exception.
    throw new YourCustomException("Put your error message here.", e);
}

指定的條件為 true 時,您可以也重新擲回例外狀況時,如下列範例所示。

catch (InvalidCastException e)
{
    if (e.Data == null)
    {
        throw;
    }
    else
    {
        // Take some action.
    }
}

注意

也可以使用例外狀況篩選,以通常更為簡潔的方式取得類似的結果 (以及未修改堆疊,如本文件稍早所述)。 下列範例的呼叫端行為類似於上一個範例。 此函式會在 e.Datanull 時,將 InvalidCastException 擲回呼叫端。

catch (InvalidCastException e) when (e.Data != null)
{
    // Take some action.
}

try 區塊內,僅初始化其中宣告的變數。 否則,在區塊的執行完成之前,可能會發生例外狀況。 例如,在下列程式碼範例中,變數 n 是在 try 區塊內初始化。 在 Write(n) 陳述式中的 try 區塊外嘗試使用此變數,將產生編譯器錯誤。

static void Main()
{
    int n;
    try
    {
        // Do not initialize this variable here.
        n = 123;
    }
    catch
    {
    }
    // Error: Use of unassigned local variable 'n'.
    Console.Write(n);
}

如需 catch 的詳細資訊,請參閱 try-catch-finally

非同步方法中的例外狀況

非同步方法會標記 async 修飾詞,而且通常包含一或多個 await 運算式或陳述式。 await 運算式會將 await 運算子套用至 TaskTask<TResult>

當控制項到達 await 方法時,方法中的進度會暫停,直到等候的工作完成。 當工作完成時,方法中的執行可以繼續。 如需詳細資訊,請參閱 使用 async 和 await 進行非同步程式設計

套用 await 完成的工作可能因為傳回工作的方法中未處理的例外狀況而處於錯誤的狀態。 等候工作擲回例外狀況。 如果傳回工作的非同步程序被取消,工作也可能以取消的狀態結束。 等候已取消的工作會擲回 OperationCanceledException

若要攔截例外狀況,請在 try 區塊中等候工作,並在關聯的 catch 區塊中攔截例外狀況。 如需範例,請參閱非同步方法範例一節。

工作可能處於錯誤狀態,因為在等候的非同步方法中發生多個例外狀況。 例如,工作可能是對 Task.WhenAll 呼叫的結果。 當您等候這類工作時,只會攔截到其中一個例外狀況,而且您無法預測會攔截的例外狀況。 如需範例,請參閱 Task.WhenAll 範例一節。

範例

在下列範例中,try 區塊包含可能會造成例外狀況的對 ProcessString 方法的呼叫。 catch 子句包含只會在螢幕上顯示訊息的例外狀況處理常式。 從 ProcessString 內呼叫 throw 陳述式時 ,系統會尋找 catch 陳述式,並顯示訊息 Exception caught

class TryFinallyTest
{
    static void ProcessString(string s)
    {
        if (s == null)
        {
            throw new ArgumentNullException(paramName: nameof(s), message: "parameter can't be null.");
        }
    }

    public static void Main()
    {
        string s = null; // For demonstration purposes.

        try
        {
            ProcessString(s);
        }
        catch (Exception e)
        {
            Console.WriteLine("{0} Exception caught.", e);
        }
    }
}
/*
Output:
System.ArgumentNullException: Value cannot be null.
   at TryFinallyTest.Main() Exception caught.
 * */

兩個 catch 區塊範例

在下列範例中,使用了兩個 catch 區塊,並會攔截會先出現的最特定例外狀況。

若要攔截最不特定的例外狀況,您可以使用下列陳述式取代 ProcessString 中的 throw 陳述式:throw new Exception()

如果您先在範例中放置最特定的 catch 區塊,會出現下列錯誤訊息:A previous catch clause already catches all exceptions of this or a super type ('System.Exception')

class ThrowTest3
{
    static void ProcessString(string s)
    {
        if (s == null)
        {
            throw new ArgumentNullException(paramName: nameof(s), message: "Parameter can't be null");
        }
    }

    public static void Main()
    {
        try
        {
            string s = null;
            ProcessString(s);
        }
        // Most specific:
        catch (ArgumentNullException e)
        {
            Console.WriteLine("{0} First exception caught.", e);
        }
        // Least specific:
        catch (Exception e)
        {
            Console.WriteLine("{0} Second exception caught.", e);
        }
    }
}
/*
 Output:
 System.ArgumentNullException: Value cannot be null.
 at Test.ThrowTest3.ProcessString(String s) ... First exception caught.
*/

非同步方法範例

下列範例說明非同步方法的例外狀況處理。 若要擷取非同步工作擲回的例外狀況,請將 await 運算式放置在 try 區塊中,並攔截 catch 區塊中的例外狀況。

取消註解範例中的 throw new Exception 行來示範例外狀況處理。 工作的 IsFaulted 屬性設定為 True,工作的 Exception.InnerException 屬性設定為例外狀況,並在 catch 區塊攔截例外狀況。

取消註解 throw new OperationCanceledException 行來示範取消非同步處理序時會發生的情況。 工作的 IsCanceled 屬性設定為 true,並在 catch 區塊攔截例外狀況。 在不適用這個範例的部分情況下,工作的 IsFaulted 屬性會設定為 trueIsCanceled 設為 false

public async Task DoSomethingAsync()
{
    Task<string> theTask = DelayAsync();

    try
    {
        string result = await theTask;
        Debug.WriteLine("Result: " + result);
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Exception Message: " + ex.Message);
    }
    Debug.WriteLine("Task IsCanceled: " + theTask.IsCanceled);
    Debug.WriteLine("Task IsFaulted:  " + theTask.IsFaulted);
    if (theTask.Exception != null)
    {
        Debug.WriteLine("Task Exception Message: "
            + theTask.Exception.Message);
        Debug.WriteLine("Task Inner Exception Message: "
            + theTask.Exception.InnerException.Message);
    }
}

private async Task<string> DelayAsync()
{
    await Task.Delay(100);

    // Uncomment each of the following lines to
    // demonstrate exception handling.

    //throw new OperationCanceledException("canceled");
    //throw new Exception("Something happened.");
    return "Done";
}

// Output when no exception is thrown in the awaited method:
//   Result: Done
//   Task IsCanceled: False
//   Task IsFaulted:  False

// Output when an Exception is thrown in the awaited method:
//   Exception Message: Something happened.
//   Task IsCanceled: False
//   Task IsFaulted:  True
//   Task Exception Message: One or more errors occurred.
//   Task Inner Exception Message: Something happened.

// Output when a OperationCanceledException or TaskCanceledException
// is thrown in the awaited method:
//   Exception Message: canceled
//   Task IsCanceled: True
//   Task IsFaulted:  False

Task.WhenAll 範例

下列範例說明多項工作可能會導致多個例外狀況的例外狀況處理。 try 區塊會等候對 Task.WhenAll 的呼叫傳回的工作。 當套用所有項目的三項工作都完成時,工作即完成。

這三個工作都會造成例外狀況。 catch 區塊會逐一查看例外狀況,這可以在 Task.WhenAll 傳回的工作的 Exception.InnerExceptions 屬性中找到。

public async Task DoMultipleAsync()
{
    Task theTask1 = ExcAsync(info: "First Task");
    Task theTask2 = ExcAsync(info: "Second Task");
    Task theTask3 = ExcAsync(info: "Third Task");

    Task allTasks = Task.WhenAll(theTask1, theTask2, theTask3);

    try
    {
        await allTasks;
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Exception: " + ex.Message);
        Debug.WriteLine("Task IsFaulted: " + allTasks.IsFaulted);
        foreach (var inEx in allTasks.Exception.InnerExceptions)
        {
            Debug.WriteLine("Task Inner Exception: " + inEx.Message);
        }
    }
}

private async Task ExcAsync(string info)
{
    await Task.Delay(100);

    throw new Exception("Error-" + info);
}

// Output:
//   Exception: Error-First Task
//   Task IsFaulted: True
//   Task Inner Exception: Error-First Task
//   Task Inner Exception: Error-Second Task
//   Task Inner Exception: Error-Third Task

C# 語言規格

如需詳細資訊,請參閱 C# 語言規格try 陳述式一節。

另請參閱