共用方式為


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

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

C# 語言參考資料記錄了 C# 語言最新版本。 同時也包含即將推出語言版本公開預覽功能的初步文件。

文件中標示了語言最近三個版本或目前公開預覽版中首次引入的任何功能。

小提示

欲查詢某功能何時首次在 C# 中引入,請參閱 C# 語言版本歷史的條目。

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;e屬性。StackTrace

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

throw 運算式

您也可以使用 throw 作為運算式。 這種方法在多種情況下可能很方便,包括:

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

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

    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 子句。

若要重新拋出被抓到的例外,請使用以下範例所示的 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;e屬性。StackTrace

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) 子句不需要是最後一個子句。

異常篩選器與傳統異常處理

與傳統的異常處理方法相比,異常過濾器具有顯著的優勢。 主要差異在於評估異常狀況處理邏輯

  • 例外狀況篩選器 (when:在取消堆疊 之前 評估篩選運算式。 這表示原始呼叫堆疊和所有局部變數在篩選評估期間保持不變。
  • 傳統 catch 區塊:抓取區塊在堆疊展開 執行,可能會遺失寶貴的除錯資訊。

以下是顯示差異的比較:

public static void DemonstrateStackUnwindingDifference()
{
    var localVariable = "Important debugging info";
    
    try
    {
        ProcessWithExceptionFilter(localVariable);
    }
    catch (InvalidOperationException ex) when (ex.Message.Contains("filter"))
    {
        // Exception filter: Stack not unwound yet.
        // localVariable is still accessible in debugger.
        // Call stack shows original throwing location.
        Console.WriteLine($"Caught with filter: {ex.Message}");
        Console.WriteLine($"Local variable accessible: {localVariable}");
    }
    
    try
    {
        ProcessWithTraditionalCatch(localVariable);
    }
    catch (InvalidOperationException ex)
    {
        // Traditional catch: Stack already unwound.
        // Some debugging information may be lost.
        if (ex.Message.Contains("traditional"))
        {
            Console.WriteLine($"Caught with if: {ex.Message}");
            Console.WriteLine($"Local variable accessible: {localVariable}");
        }
        else
        {
            throw; // Re-throws and further modifies stack trace.
        }
    }
}

private static void ProcessWithExceptionFilter(string context)
{
    throw new InvalidOperationException($"Exception for filter demo: {context}");
}

private static void ProcessWithTraditionalCatch(string context)
{
    throw new InvalidOperationException($"Exception for traditional demo: {context}");
}

異常篩選器的優點

  • 更好的偵錯體驗:由於堆疊在篩選相符之前不會回復,因此偵錯工具可以顯示原始失敗點,且所有區域變數都完好無損。
  • 效能優勢:如果沒有相符的篩選條件,例外狀況會繼續傳播,而不會產生堆疊回想和還原的額外負荷。
  • 更乾淨的程式碼:多個篩選器可以處理相同異常類型的不同條件,而不需要巢狀的 if-else 陳述式。
  • 記錄和診斷:在決定是否處理異常之前,您可以檢查並記錄異常詳細資料:
public static void DemonstrateDebuggingAdvantage()
{
    var contextData = new Dictionary<string, object>
    {
        ["RequestId"] = Guid.NewGuid(),
        ["UserId"] = "user123",
        ["Timestamp"] = DateTime.Now
    };

    try
    {
        // Simulate a deep call stack.
        Level1Method(contextData);
    }
    catch (Exception ex) when (LogAndFilter(ex, contextData))
    {
        // This catch block may never execute if LogAndFilter returns false.
        // But LogAndFilter can examine the exception while the stack is intact.
        Console.WriteLine("Exception handled after logging");
    }
}

private static void Level1Method(Dictionary<string, object> context)
{
    Level2Method(context);
}

private static void Level2Method(Dictionary<string, object> context)
{
    Level3Method(context);
}

private static void Level3Method(Dictionary<string, object> context)
{
    throw new InvalidOperationException("Error in deep call stack");
}

private static bool LogAndFilter(Exception ex, Dictionary<string, object> context)
{
    // This method runs before stack unwinding.
    // Full call stack and local variables are still available.
    Console.WriteLine($"Exception occurred: {ex.Message}");
    Console.WriteLine($"Request ID: {context["RequestId"]}");
    Console.WriteLine($"Full stack trace preserved: {ex.StackTrace}");
    
    // Return true to handle the exception, false to continue search.
    return ex.Message.Contains("deep call stack");
}

何時使用例外狀況篩選

當您需要時,請使用例外狀況篩選:

  • 根據特定條件或屬性處理異常。
  • 保留原始呼叫堆疊以進行偵錯。
  • 在決定是否處理例外狀況之前,先記錄或檢查例外狀況。
  • 根據內容以不同的方式處理相同的例外類型。
public static void HandleFileOperations(string filePath)
{
    try
    {
        // Simulate file operation that might fail.
        ProcessFile(filePath);
    }
    catch (IOException ex) when (ex.Message.Contains("access denied"))
    {
        Console.WriteLine("File access denied. Check permissions.");
    }
    catch (IOException ex) when (ex.Message.Contains("not found"))
    {
        Console.WriteLine("File not found. Verify the path.");
    }
    catch (IOException ex) when (IsNetworkPath(filePath))
    {
        Console.WriteLine($"Network file operation failed: {ex.Message}");
    }
    catch (IOException)
    {
        Console.WriteLine("Other I/O error occurred.");
    }
}

private static void ProcessFile(string filePath)
{
    // Simulate different types of file exceptions.
    if (filePath.Contains("denied"))
        throw new IOException("File access denied");
    if (filePath.Contains("missing"))
        throw new IOException("File not found");
    if (IsNetworkPath(filePath))
        throw new IOException("Network timeout occurred");
}

private static bool IsNetworkPath(string path)
{
    return path.StartsWith(@"\\") || path.StartsWith("http");
}

堆疊追蹤保留

例外狀況篩選會保留原始 ex.StackTrace 屬性。 如果某 catch 個子句無法處理異常並重新拋出,原始堆疊資訊就會遺失。 篩選器when不會展開堆疊,因此如果篩選器是 when,則false不會變更原始堆疊追蹤。

例外過濾器方法在保存除錯資訊對診斷程式碼錯誤至關重要的應用中非常有價值。

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

若異例發生於 非同步函式,異常會在你 等待 函式結果時傳達給該函式的呼叫者,如下範例所示:

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# 語言規格的下列幾節:

另請參閱