探討如何在 C# 中建立和擲出例外狀況
- 16 分鐘
.NET 提供衍生自基類的 System.Exception 例外狀況類別階層。 C# 應用程式可以建立及擲回任何例外狀況類型的例外狀況。 開發人員也可以藉由指派屬性值來使用應用程式特定資訊自訂例外狀況物件。
備註
本課程模組著重於建立和擲回例外狀況,以及自定義例外狀況物件。 建立自定義例外狀況類別超出此課程模組的範圍。
建立例外狀況物件
從程式代碼內建立和擲回例外狀況是 C# 程序設計的重要層面。 產生例外狀況以回應特定條件、問題或錯誤的能力可協助您確保應用程式的穩定性。
您所建立的例外狀況類型取決於程式碼問題,而且應該盡可能符合例外狀況的預期用途。
例如,假設您正在建立名為 GraphData 的方法,以執行數據分析。 方法會接收數據陣列做為輸入參數。 方法預期輸入數據在特定範圍內。 如果方法收到超出預期範圍的數據,它會建立並擲回 類型的例外狀況 ArgumentException。 例外狀況會由負責提供數據的程式代碼在呼叫堆疊的某處處理。
以下是建立例外狀況時可能會使用的一些常見例外狀況類型:
ArgumentException或ArgumentNullException:當呼叫具有無效自變數值或 Null 參考的方法或建構函式時,請使用這些例外狀況類型。InvalidOperationException:當方法的作業條件不支援特定方法呼叫成功完成時,請使用這個例外狀況類型。NotSupportedException:不支援作業或功能時,請使用此例外狀況類型。IOException:當輸入/輸出作業失敗時,請使用此例外狀況類型。FormatException:當字串或數據的格式不正確時,請使用這個例外狀況類型。
關鍵詞 new 是用來建立例外狀況的實例。 例如,您可以建立例外狀況類型的實例 ArgumentException ,如下所示:
ArgumentException invalidArgumentException = new ArgumentException();
設定和拋出自定義例外狀況
擲回例外狀況對象的程式牽涉到建立例外狀況衍生類別的實例、選擇性地設定例外狀況的屬性,然後使用 關鍵詞擲回物件 throw 。
在擲回例外狀況之前,使用內容相關資訊來自訂例外狀況通常很有幫助。 您可以藉由設定其屬性,在例外狀況物件內提供應用程式特定資訊。 例如,下列程式代碼會使用自定義invalidArgumentException屬性建立名為 Message 的例外狀況對象,然後擲回例外狀況:
ArgumentException invalidArgumentException = new ArgumentException("ArgumentException: The 'GraphData' method received data outside the expected range.");
throw invalidArgumentException;
備註
例外狀況的 Message 屬性是唯讀的。 因此,在具現化 物件時,必須設定自定義 Message 屬性。
自定義例外狀況物件時,請務必提供清楚的錯誤訊息來描述問題以及如何加以解決。 您也可以包含其他資訊,例如堆疊追蹤和錯誤碼,以協助使用者修正問題。
可以直接在 throw 語句中建立例外狀況物件。 例如:
throw new FormatException("FormatException: Calculations in process XYZ have been cancelled due to invalid data format.");
擲回例外狀況時,要記住的一些考量事項包括:
- 屬性
Message應該說明例外狀況的原因。 不過,敏感性或代表安全性考慮的信息不應該放在消息正文中。 - 屬性
StackTrace通常用來追蹤例外狀況的來源。 這個字串屬性包含目前呼叫堆疊上方法的名稱,以及與例外狀況相關聯之每個方法的檔名和行號。 Common Language Runtime (CLR)會從StackTrace語句開始自動建立throw物件。 例外狀況必須從堆疊追蹤開始的位置擲回。
拋出例外狀況的時機
方法應該會在無法完成其預定用途時拋出例外。 擲回的例外狀況應以符合錯誤條件的最特定例外狀況為基礎。
假設開發人員正在處理實作商務程式的應用程式。 商務程式相依於用戶輸入。 如果輸入不符合預期的數據類型,則實作商務程式的方法會建立並擲回例外狀況。 例外狀況物件可以使用屬性值中的應用程式特定信息來設定。 下列程式代碼範例示範案例:
string[][] userEnteredValues = new string[][]
{
new string[] { "1", "two", "3"},
new string[] { "0", "1", "2"}
};
foreach (string[] userEntries in userEnteredValues)
{
try
{
BusinessProcess1(userEntries);
}
catch (Exception ex)
{
if (ex.StackTrace.Contains("BusinessProcess1") && (ex is FormatException))
{
Console.WriteLine(ex.Message);
}
}
}
static void BusinessProcess1(string[] userEntries)
{
int valueEntered;
foreach (string userValue in userEntries)
{
try
{
valueEntered = int.Parse(userValue);
// completes required calculations based on userValue
// ...
}
catch (FormatException)
{
FormatException invalidFormatException = new FormatException("FormatException: User input values in 'BusinessProcess1' must be valid integers");
throw invalidFormatException;
}
}
}
在此程式代碼範例中,最上層語句會呼叫 BusinessProcess1 方法,並傳入包含使用者輸入值的字串數位列。 方法 BusinessProcess1 預期使用者輸入值可以轉換成整數。 當方法遇到格式無效的數據時,它會使用自定義FormatException屬性建立例外狀況類型的實例Message。 然後,方法會擲回例外狀況。 在最上層陳述式中,例外狀況會以名稱為 ex 的物件形式加以攔截。 在向使用者顯示例外狀況訊息之前, ex 會先檢查對象的屬性。 首先,程式碼會檢查 StackTrace 屬性,以查看它是否包含 "BusinessProcess1"。 其次,例外狀況對象 ex 會驗證為 類型 FormatException。
重新擲回例外狀況
除了可以擲回新的例外狀況之外,throw 還可以用於從 catch 程式碼區塊內重新擲回例外狀況。 在此情況下,throw 不接受例外運算元。
catch (Exception ex)
{
// handle or partially handle the exception
// ...
// re-throw the original exception object for further handling down the call stack
throw;
}
當您重新擲回例外狀況時,會使用原始例外狀況物件,因此不會遺失例外狀況的任何資訊。 如果您想要建立包裝原始例外狀況的新例外狀況物件,您可以將原始例外狀況當做自變數傳遞至新例外狀況對象的建構函式。 例如:
catch (Exception ex)
{
// handle or partially handle the exception
// ...
// create a new exception object that wraps the original exception
throw new ApplicationException("An error occurred", ex);
}
針對 「BusinessProcess1」 應用程式案例,請考慮下列更新:
- 方法
BusinessProcess1已更新以包含其他詳細數據。BusinessProcess1現在遇到兩個問題,而且必須為每個問題產生例外狀況。 - 已更新最上層陳述式。 最上層語句現在會呼叫
OperatingProcedure1方法。OperatingProcedure1在程式BusinessProcess1代碼區塊內呼叫try。 OperatingProcedure1方法可以處理其中一個例外狀況類型,並部分處理另一個。 處理部分處理的例外狀況之後,OperatingProcedure1必須重新擲回原始例外狀況。
下列程式代碼範例示範已更新的案例:
try
{
OperatingProcedure1();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Exiting application.");
}
static void OperatingProcedure1()
{
string[][] userEnteredValues = new string[][]
{
new string[] { "1", "two", "3"},
new string[] { "0", "1", "2"}
};
foreach(string[] userEntries in userEnteredValues)
{
try
{
BusinessProcess1(userEntries);
}
catch (Exception ex)
{
if (ex.StackTrace.Contains("BusinessProcess1"))
{
if (ex is FormatException)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Corrective action taken in OperatingProcedure1");
}
else if (ex is DivideByZeroException)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Partial correction in OperatingProcedure1 - further action required");
// re-throw the original exception
throw;
}
else
{
// create a new exception object that wraps the original exception
throw new ApplicationException("An error occurred - ", ex);
}
}
}
}
}
static void BusinessProcess1(string[] userEntries)
{
int valueEntered;
foreach (string userValue in userEntries)
{
try
{
valueEntered = int.Parse(userValue);
checked
{
int calculatedValue = 4 / valueEntered;
}
}
catch (FormatException)
{
FormatException invalidFormatException = new FormatException("FormatException: User input values in 'BusinessProcess1' must be valid integers");
throw invalidFormatException;
}
catch (DivideByZeroException)
{
DivideByZeroException unexpectedDivideByZeroException = new DivideByZeroException("DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero");
throw unexpectedDivideByZeroException;
}
}
}
更新的範例程式代碼會產生下列輸出:
FormatException: User input values in 'BusinessProcess1' must be valid integers
Corrective action taken in OperatingProcedure1
DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero
Partial correction in OperatingProcedure1 - further action required
DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero
Exiting application.
拋出例外狀況時應避免的事項
下列清單可識別擲回例外狀況時要避免的做法:
- 請勿使用例外狀況來變更程式流程,作為一般執行的一部分。 使用例外狀況來報告及處理錯誤狀況。
- 例外狀況不應以傳回值或參數形式傳回,而是應該加以擲回。
- 請勿刻意從您自己的原始程式碼擲回
System.Exception、System.SystemException、System.NullReferenceException或System.IndexOutOfRangeException。 - 請勿建立可在偵錯模式中擲回但無法以發行模式擲回的例外狀況。 若要在開發階段識別執行時間錯誤,請改用
Debug.Assert。
備註
Debug.Assert 方法是開發期間用來攔截邏輯錯誤的工具。 根據預設, Debug.Assert 方法只能在偵錯組建中運作。 您可以在 Debug.Assert 偵錯會話中使用 來檢查不應該發生的條件。 方法會採用兩個參數:要檢查的布爾條件,以及選擇性字串訊息,以在條件為 false時顯示。 Debug.Assert 不應該用來取代擲回例外狀況,這是在程式代碼正常執行期間處理例外狀況的方法。 您應該使用 Debug.Assert 來攔截不應該發生的錯誤,並使用例外狀況來處理程式正常執行期間可能發生的錯誤。
回顧
以下為本單元須記住的一些重點:
- 建立和擲回例外狀況時,例外狀況類型必須盡可能符合例外狀況的預期用途。
- 在
throw例外狀況中,您可以建立例外狀況衍生類別的實例、設定其屬性,然後使用throw關鍵詞。 - 建立例外狀況物件時,請務必提供清楚的錯誤訊息和其他資訊,以協助使用者修正問題。