跳至頁尾內容
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 的開發者建立內容?將您的專業知識轉化為額外收入!

Iron 支援團隊

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