跳過到頁腳內容
Iron Academy Logo
學習 C#
學習 C#

其他分類

C# 例外處理

Tim Corey
59m 46s

例外處理是健壯應用程式開發的重要方面。 Tim Corey 的影片 "處理 C# 中的例外 - 何時捕捉它們,在哪裡捕捉它們,以及如何捕捉它們" 詳細說明了什麼是例外、如何處理它們以及在哪裡處理它們。

本文旨在使用 Tim Corey 的影片來解釋 C# 中的例外處理。 這是一個強大的功能,允許開發者管理程式執行過程中出現的錯誤和異常情況。 透過使用 try、catch 和 finally 區塊,C# 提供了一種結構化的方式來處理運行時錯誤、記錄例外,並維持程式流程。

簡介

Tim 首先解釋了許多開發者對例外及其處理的錯誤看法。 他強調理解什麼是例外,以及正確處理它們的方式和地方的重要性,以創建更強壯的應用程式。

構建範例控制台應用程式

Tim 在 Visual Studio 2017 中創建了一個控制台應用程式以演示例外處理。 他建議使用控制台應用程式來測試新主題,因為它們需要的設置最少,而且易於使用。

using System;

namespace ExceptionsDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // Placeholder for input and output operations
            Console.ReadLine();
        }
    }
}
using System;

namespace ExceptionsDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // Placeholder for input and output operations
            Console.ReadLine();
        }
    }
}

創建一個類別庫

Tim 為解決方案新增了一個類別庫,以模擬現實世界中不同方法相互調用的場景。

Csharp Exception Handling 1 related to 創建一個類別庫

他刪除了預設類並創建了一個名為 DemoCode 的新類。

public class DemoCode
{
    // Method to retrieve a number based on the provided position
    public int GetNumber(int position)
    {
        int[] numbers = { 1, 4, 7, 2 };
        return numbers[position];
    }

    // Intermediate method calls GetNumber
    public int ParentMethod(int position)
    {
        return GetNumber(position);
    }

    // Top-level method calls ParentMethod
    public int GrandparentMethod(int position)
    {
        return ParentMethod(position);
    }
}
public class DemoCode
{
    // Method to retrieve a number based on the provided position
    public int GetNumber(int position)
    {
        int[] numbers = { 1, 4, 7, 2 };
        return numbers[position];
    }

    // Intermediate method calls GetNumber
    public int ParentMethod(int position)
    {
        return GetNumber(position);
    }

    // Top-level method calls ParentMethod
    public int GrandparentMethod(int position)
    {
        return ParentMethod(position);
    }
}

DemoCode 類包含相互調用的方法,最終根據給定的位置從陣列中檢索一個數字。

模擬一個例外

Tim 解釋說,此應用程式旨在演示失敗而非成功。 他通過向 GrandparentMethod 傳遞無效位置來引入一個越界例外。

DemoCode demo = new DemoCode();
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
DemoCode demo = new DemoCode();
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");

以無效位置運行上述代碼會導致出現 IndexOutOfRangeException。 Tim 展示了 Visual Studio 偵錯器如何突出顯示問題並提供有關例外的詳細信息。

不應這樣使用 try-catch

Tim 解釋了開發者在首次學習 try-catch 區塊時常犯的錯誤。 他們通常會包裝整個代碼塊,而預期可能會發生例外,這會導致不當處理。

try
{
    int output = 0;
    output = numbers[position];
    return output;
}
catch (Exception ex)
{
    // Avoid returning default values that can mask the problem
    return 0;
}
try
{
    int output = 0;
    output = numbers[position];
    return output;
}
catch (Exception ex)
{
    // Avoid returning default values that can mask the problem
    return 0;
}

Tim 指出,此方法存在問題,因為它隱藏了例外,並在錯誤假設下繼續執行。 例如,返回 0 作為預設值可能並不合適,並可能導致進一步問題。

正確的例外處理

Tim 強調例外提供了有關應用程式中意外狀態的重要信息。 如果應用程式在這種狀態下繼續執行而未得到正確處理,可能會導致進一步的錯誤和數據損壞。

而不是吞下例外,重要的是要妥善處理它們。 這是更好的方法:

try
{
    return numbers[position];
}
catch (Exception ex)
{
    // Log the exception or handle it appropriately
    Console.WriteLine(ex.Message);
    throw; // Re-throw the exception to be handled by a higher-level handler
}
try
{
    return numbers[position];
}
catch (Exception ex)
{
    // Log the exception or handle it appropriately
    Console.WriteLine(ex.Message);
    throw; // Re-throw the exception to be handled by a higher-level handler
}

通過重新拋出例外,您可以確保問題被傳播,如果需要,可以在更高層級處理。

向用戶提供有用的信息

Tim 解釋說,雖然某些例外可以在不崩潰應用程式的情況下妥善處理,但重要的是為用戶提供有用的反饋。 例如,顯示一個消息框或通知,並提供重新嘗試操作的選項。

更有用的信息:StackTrace

Tim 演示瞭如何使用例外對象的 StackTrace 屬性以獲得有關例外發生位置的詳細信息。 這包括類別、方法和行號,這對於調試至關重要。

try
{
    return numbers[position];
}
catch (Exception ex)
{
    Console.WriteLine(ex.StackTrace);
    throw;
}
try
{
    return numbers[position];
}
catch (Exception ex)
{
    Console.WriteLine(ex.StackTrace);
    throw;
}

StackTrace 屬性提供了一個完整的調用棧程式追蹤,幫助開發者精確定位問題的位置。

適當放置 try-catch

Tim 解釋說,正確處理例外不僅僅是捕捉它們,還包括知道如何放置您的 try-catch 區塊。 關鍵是將 try-catch 區塊放置在您擁有足夠上下文來適當處理例外的層級。

不當放置的示例

將 try-catch 區塊放置在較深的調用堆疊中往往無法有效處理例外,因為您缺乏高級別操作的上下文。

// Deep level exception handling (not ideal)
try
{
    return numbers[position];
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw;
}
// Deep level exception handling (not ideal)
try
{
    return numbers[position];
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw;
}

適當放置的示例

將 try-catch 區塊放置在頂層,例如在用戶界面或應用程序的入口點,可以確保您在操作的完整上下文中處理例外。

try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

這樣,您可以向用戶提供更具資訊性的消息,並決定授使用程序繼續運行還是應終止。

堆疊追蹤信息

Tim 強調堆疊追蹤信息在診斷例外中的重要性。 堆疊追蹤提供了詳細的調用歷史,顯示了例外發生的位置以及導致問題的方法調用鏈。

try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

這個輸出提供了例外的確切位置和代碼的執行路徑,使調試和修復問題更加容易。

處理邏輯演示

Tim 演示如何在適當的層次處理邏輯。 例如,如果一個方法負責打開和關閉資料庫連接,它應該處理例外,以確保資源得到正確管控。

public int GrandparentMethod(int position)
{
    try
    {
        Console.WriteLine("Open database connection");
        int output = ParentMethod(position);
        Console.WriteLine("Close database connection");
        return output;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Ensure the exception is propagated
    }
}
public int GrandparentMethod(int position)
{
    try
    {
        Console.WriteLine("Open database connection");
        int output = ParentMethod(position);
        Console.WriteLine("Close database connection");
        return output;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Ensure the exception is propagated
    }
}

在此示例中,如果發生例外,則資料庫連接未正確關閉,可能會導致資源洩漏。 透過新增 try-catch 區塊,您可以確保即使發生例外,連接也會關閉。

使用 finally 區塊

Tim 介紹了 finally 區塊,該區塊可確保無論是否發生例外,某些代碼都會執行。 這對於清理資源特別重要,例如關閉資料庫連接。

public int GrandparentMethod(int position)
{
    try
    {
        Console.WriteLine("Open database connection");
        int output = ParentMethod(position);
        return output;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Re-throw the exception to ensure it's handled by a higher-level handler
    }
    finally
    {
        Console.WriteLine("Close database connection");
    }
}
public int GrandparentMethod(int position)
{
    try
    {
        Console.WriteLine("Open database connection");
        int output = ParentMethod(position);
        return output;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw; // Re-throw the exception to ensure it's handled by a higher-level handler
    }
    finally
    {
        Console.WriteLine("Close database connection");
    }
}

finally 區塊在 try 和 catch 區塊之後運行,確保即使拋出了例外,連接也會關閉。

throw 語句

Tim 解釋了重新拋出例外以將它們傳遞給調用堆疊的重要性。 這使得高層級的處理器可以適當地處理例外。

catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw; // Re-throws the exception to be handled by the calling method
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    throw; // Re-throws the exception to be handled by the calling method
}

用 throw; 重新拋出例外 確保完整的堆疊追蹤已保留,提供了調試的重要上下文。

正確傳遞例外

Tim 演示了例外如何通過調用堆疊冒泡。 每個方法都會檢查是否有 try-catch 區塊,並決定是處理例外還是將其傳遞給調用者。

try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
    Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

在此範例中,GrandparentMethod 捕獲例外、記錄並重新拋出。 控制台應用程式中的頂級 try-catch 區塊然後處理例外並顯示錯誤消息和堆疊追蹤。

例外處理中的常見錯誤

Tim 突出顯示開發者在處理例外時常犯的幾個錯誤:

  1. 使用 throw ex;:

    • 重寫堆疊追蹤並丟失寶貴的上下文。

    • 例子:
      catch (Exception ex)
      {
      // Incorrect
      throw ex; // Rewrites stack trace
      }
      catch (Exception ex)
      {
      // Incorrect
      throw ex; // Rewrites stack trace
      }
  2. 拋出一個新的例外

    • 創建一個帶有自定義消息的新例外,但丟失了原始堆疊追蹤。

    • 例子:
      catch (Exception ex)
      {
      // Incorrect
      throw new Exception("I blew up");
      }
      catch (Exception ex)
      {
      // Incorrect
      throw new Exception("I blew up");
      }

創建新例外而不丟失原始堆疊追蹤

Tim 解釋了如何創建新的例外,同時保留原始堆疊追蹤。 當您希望提供更有意義的錯誤消息或不同的例外類型時,這可能很有用,同時仍保留原始錯誤的上下文。

catch (Exception ex)
{
    throw new ArgumentException("You passed in bad data", ex);
}
catch (Exception ex)
{
    throw new ArgumentException("You passed in bad data", ex);
}

透過將原始例外 (ex) 作為內部例外傳遞,您保留了原始的堆疊追蹤,這對於調試至關重要。

保留堆疊追蹤信息

Tim 演示了如何在創建新例外時訪問原始例外的消息和堆疊追蹤。

catch (Exception ex)
{
    Console.WriteLine("You passed in bad data");
    Console.WriteLine(ex.StackTrace);
    throw new ArgumentException("You passed in bad data", ex);
}
catch (Exception ex)
{
    Console.WriteLine("You passed in bad data");
    Console.WriteLine(ex.StackTrace);
    throw new ArgumentException("You passed in bad data", ex);
}

這保證了堆疊中的例外包含了新消息和原始例外詳情。

循環遍歷內部例外

Tim 提供了一種方法來循環遍歷所有內部例外,以提取它們的消息和堆疊追蹤。

catch (Exception ex)
{
    Exception inner = ex;
    while (inner != null)
    {
        Console.WriteLine(inner.StackTrace);
        inner = inner.InnerException;
    }
    throw;
}
catch (Exception ex)
{
    Exception inner = ex;
    while (inner != null)
    {
        Console.WriteLine(inner.StackTrace);
        inner = inner.InnerException;
    }
    throw;
}

這個循環遍歷每個內部例外,打印其堆疊追蹤,確保所有層次的例外均被考慮。

不同例外的不同處理

Tim 討論了如何使用多個 catch 區塊來處理不同類型的例外。 這允許根據例外類型進行具體處理。

try
{
    // Code that might throw an exception
}
catch (ArgumentException ex)
{
    Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    // Code that might throw an exception
}
catch (ArgumentException ex)
{
    Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

在此示例中,ArgumentException 得到了特定的處理,通過打印自定義消息,而所有其他例外則回退到一般處理程序,打印例外消息和堆疊追蹤。

Csharp Exception Handling 2 related to 不同例外的不同處理

多個 catch 區塊中的順序重要性

Tim 強調使用多個 catch 區塊時順序的重要性。 最具體的例外應首先被捕捉,然後是更加一般的例外。

try
{
    // Code that might throw an exception
}
catch (ArgumentException ex)
{
    Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}
try
{
    // Code that might throw an exception
}
catch (ArgumentException ex)
{
    Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
    Console.WriteLine(ex.StackTrace);
}

如果更一般的 catch 區塊出現在具體的區塊之前,它將捕抓所有例外,而具體的 catch 區塊將無法到達,導致編譯錯誤。

結論

Tim Corey 的進階 影片指南討論了 C# 中的例外處理關鍵技術,包括創建新例外、保留堆疊追蹤及有效使用多個 catch 區塊。 通過遵循他的最佳實踐,開發者可以創建處理例外優雅並提供有用調試信息的健壯應用程式。

Hero Worlddot related to C# 例外處理
Hero Affiliate related to C# 例外處理

通過分享您所愛的東西賺得更多

您是否在為使用.NET、C#、Java、Python或Node.js的開發者創建內容?將您的專業知識轉化為額外收入!

鋼鐵支援團隊

我們每週 5 天,每天 24 小時在線上。
聊天
電子郵件
打電話給我