C# 例外處理
例外處理是健壯應用程式開發的重要方面。 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 為解決方案新增了一個類別庫,以模擬現實世界中不同方法相互調用的場景。

他刪除了預設類並創建了一個名為 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 突出顯示開發者在處理例外時常犯的幾個錯誤:
-
使用 throw ex;:
-
重寫堆疊追蹤並丟失寶貴的上下文。
- 例子:
catch (Exception ex) { // Incorrect throw ex; // Rewrites stack trace }catch (Exception ex) { // Incorrect throw ex; // Rewrites stack trace }
-
-
拋出一個新的例外:
-
創建一個帶有自定義消息的新例外,但丟失了原始堆疊追蹤。
- 例子:
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 得到了特定的處理,通過打印自定義消息,而所有其他例外則回退到一般處理程序,打印例外消息和堆疊追蹤。

多個 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 區塊。 通過遵循他的最佳實踐,開發者可以創建處理例外優雅並提供有用調試信息的健壯應用程式。
