例外狀況處理陳述式 - throwtry-catchtry-finallytry-catch-finally

您可以使用 throwtry 陳述式來處理例外狀況。 使用 throw 陳述式 來擲回例外狀況。 使用 try 陳述式 來攔截並處理程式碼區塊執行期間可能發生的例外狀況。

throw 陳述式

throw 陳述式會擲回例外狀況:

if (shapeAmount <= 0)
{
    throw new ArgumentOutOfRangeException(nameof(shapeAmount), "Amount of shapes must be positive.");
}

throw e; 陳述式中,運算式 e 的結果必須隱含轉換成 System.Exception

您可以使用內建例外狀況類別,例如 ArgumentOutOfRangeExceptionInvalidOperationException。 .NET 也提供協助程式方法,以在特定情況下擲回例外狀況:ArgumentNullException.ThrowIfNullArgumentException.ThrowIfNullOrEmpty。 您也可以定義衍生自 System.Exception 的自有例外狀況類別。 如需詳細資訊,請參閱建立和擲回例外狀況

catch 區塊內,您可以使用 throw; 陳述式來重新擲回 catch 區塊所處理的例外狀況:

try
{
    ProcessShapes(shapeAmount);
}
catch (Exception e)
{
    LogError(e, "Shape processing failed.");
    throw;
}

注意

throw; 會保留例外狀況的原始堆疊追蹤,其儲存在 Exception.StackTrace 屬性中。 相反地,throw e; 會更新 eStackTrace 屬性。

擲回例外狀況時,Common Language Runtime (CLR) 會尋找可處理此例外狀況的 catch 區塊。 如果目前執行的方法不包含這類 catch 區塊,CLR 會在呼叫堆疊上查看呼叫目前方法的方法,依此類推。 如果找不到 catch 區塊,CLR 就會終止執行中的執行緒。 如需詳細資訊,請參閱 C# 語言規格如何處理例外狀況一節。

throw 運算式

您也可以使用 throw 作為運算式。 這在一些案例中可能很方便,包括:

  • 條件運算子。 下列範例會使用 throw 運算式,在傳遞的陣列 args 空白時擲回 ArgumentException

    string first = args.Length >= 1 
        ? args[0]
        : throw new ArgumentException("Please supply at least one argument.");
    
  • Null 聯合運算子。 下列範例會使用 throw 運算式,在要指派給屬性的字串為 null 時擲回 ArgumentNullException

    public string Name
    {
        get => name;
        set => name = value ??
            throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null");
    }
    
  • 運算式主體 ambda 或方法。 下列範例會使用 throw 運算式來擲回 InvalidCastException,指出不支援轉換成 DateTime 值:

    DateTime ToDateTime(IFormatProvider provider) =>
             throw new InvalidCastException("Conversion to a DateTime is not supported.");
    

try 陳述式

您可以使用下列任何形式的 try 陳述式:try-catch - 處理在 try 區塊內執行程式碼時可能發生的例外狀況,try-finally - 指定當控制項離開 try 區塊時執行的程式碼,以及 try-catch-finally - 做為上述兩種形式的組合。

try-catch 陳述式

使用 try-catch 陳述式 來處理程式碼區塊執行期間可能發生的例外狀況。 將可能發生例外狀況的程式碼放在 try 區塊內。 使用 catch 子句來指定您想要在對應 catch 區塊中處理的例外狀況基底類型:

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}

您可以提供數個 catch 子句:

try
{
    var result = await ProcessAsync(-3, 4, cancellationToken);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}
catch (OperationCanceledException)
{
    Console.WriteLine("Processing is cancelled.");
}

發生例外狀況時,會以指定的順序 (從上到下) 檢查 catch 子句。 最多只會針對任何擲回的例外狀況執行一個 catch 區塊。 如上述範例所示,您可以省略例外狀況變數的宣告,只在 catch 子句中指定例外狀況類型。 不含任何指定例外狀況類型的 catch 子句符合任何例外狀況,如果存在,則必須是最後一個 catch 子句。

如果您想要重新擲回攔截到的例外狀況,請使用 throw 陳述式,如下列範例所示:

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e)
{
    LogError(e, "Processing failed.");
    throw;
}

注意

throw; 會保留例外狀況的原始堆疊追蹤,其儲存在 Exception.StackTrace 屬性中。 相反地,throw e; 會更新 eStackTrace 屬性。

when 例外狀況篩選條件

除了例外狀況類型,您也可以指定例外狀況篩選條件,進一步檢查例外狀況,並決定對應的 catch 區塊是否處理該例外狀況。 例外狀況篩選條件是接在 when 關鍵字之後的布林運算式,如下列範例所示:

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e) when (e is ArgumentException || e is DivideByZeroException)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}

上述範例會使用例外狀況篩選來提供單一 catch 區塊,以處理兩個指定類型的例外狀況。

您可以為相同的例外狀況類型提供數個 catch 子句,前提是這些子句是由例外狀況篩選條件區別。 其中一個子句可能沒有任何例外狀況篩選條件。 如果這類子句存在,其必須是指定該例外狀況類型的最後一個子句。

如果 catch 子句具有例外狀況篩選條件,其指定的例外狀況類型可與出現在其後之 catch 子句的例外狀況類型相同或衍生程度較低。 例如,如果例外狀況篩選條件存在,則 catch (Exception e) 子句不需要是最後一個子句。

非同步和迭代器方法的例外狀況

如果在非同步函式中發生例外狀況,當您 await 函式的結果時,它會傳播至函式的呼叫端,如下列範例所示:

public static async Task Run()
{
    try
    {
        Task<int> processing = ProcessAsync(-1);
        Console.WriteLine("Launched processing.");

        int result = await processing;
        Console.WriteLine($"Result: {result}.");
    }
    catch (ArgumentException e)
    {
        Console.WriteLine($"Processing failed: {e.Message}");
    }
    // Output:
    // Launched processing.
    // Processing failed: Input must be non-negative. (Parameter 'input')
}

private static async Task<int> ProcessAsync(int input)
{
    if (input < 0)
    {
        throw new ArgumentOutOfRangeException(nameof(input), "Input must be non-negative.");
    }

    await Task.Delay(500);
    return input;
}

如果迭代器方法發生例外狀況,則只有在迭代器進到下一個元素時,才會傳播至呼叫端。

try-finally 陳述式

try-finally 陳述式中,當控制項離開 try 區塊時,就會執行 finally 區塊。 控制項可能會由於下列事項而離開 try 區塊:

  • 正常執行,
  • 執行 跳躍陳述式 (也就是 returnbreakcontinuegoto),或
  • 傳播 try 區塊以外的例外狀況。

下列範例會使用 finally 區塊,在控制項離開方法之前重設物件的狀態:

public async Task HandleRequest(int itemId, CancellationToken ct)
{
    Busy = true;

    try
    {
        await ProcessAsync(itemId, ct);
    }
    finally
    {
        Busy = false;
    }
}

您也可以使用 finally 區塊來清除 try 區塊中使用的配置資源。

注意

當資源的類型實作 IDisposableIAsyncDisposable 介面時,請考慮 using 陳述式using 陳述式可確保當控制項離開 using 陳述式時,會處置已取得的資源。 編譯器會將 using 陳述式轉換成 try-finally 陳述式。

在幾乎所有情況下,都會執行 finally 區塊。 未執行 finally 區塊的唯一情況涉及立即終止程式。 例如,這類終止可能會因為 Environment.FailFast 呼叫或 OverflowExceptionInvalidProgramException 例外狀況而發生。 大部分的作業系統會在停止和卸載流程中執行合理的資源清除。

try-catch-finally 陳述式

您可以使用 try-catch-finally 陳述式來處理 try 區塊執行期間可能發生的例外狀況,並指定當控制項離開 try 陳述式時必須執行的程式碼:

public async Task ProcessRequest(int itemId, CancellationToken ct)
{
    Busy = true;

    try
    {
        await ProcessAsync(itemId, ct);
    }
    catch (Exception e) when (e is not OperationCanceledException)
    {
        LogError(e, $"Failed to process request for item ID {itemId}.");
        throw;
    }
    finally
    {
        Busy = false;
    }

}

catch 區塊處理例外狀況時,finally 區塊會在執行該 catch 區塊之後執行 (即使執行 catch 區塊期間發生另一個例外狀況也一樣)。 如需 catchfinally 區塊的相關資訊,請分別參閱try-catch 陳述式try-finally 陳述式章節。

C# 語言規格

如需詳細資訊,請參閱 C# 語言規格的下列幾節:

另請參閱