創建和引發異常
異常用於指示在運行程序時發生了錯誤。此時將創建一個描述錯誤的異常對象,然後使用 throw 關鍵字“引發”該對象。然後運行時搜索最兼容的異常處理程序。
當存在下列一種或多種情況時,程序員應引發異常:
方法無法完成其中定義的功能。
例如,如果方法的參數具有無效值:
static
void
CopyObject(SampleClass original)
{
if
(original ==
null
)
{
throw
new
System.ArgumentException(
"Parameter cannot be null"
,
"original"
);
}
}
根據對象的狀態,對某個對象進行不適當的調用。
一個示例可能嘗試對只讀文件執行寫操作。在對象狀態不允許某項操作的情況下,引發 InvalidOperationException 的一個實例或基於此類的派生類的對象。以下為引發 InvalidOperationException 對象的方法的示例:
class
ProgramLog
{
System.IO.FileStream logFile =
null
;
void
OpenLog(System.IO.FileInfo fileName, System.IO.FileMode mode) {}
void
WriteLog()
{
if
(!
this
.logFile.CanWrite)
{
throw
new
System.InvalidOperationException(
"Logfile cannot be read-only"
);
}
// Else write data to the log and return.
}
}
方法的參數導致了異常。
在此情況下,應捕獲原始異常並創建一個 ArgumentException 實例。原始異常應作為 InnerException 參數傳遞給 ArgumentException 的構造函數:
static
int
GetValueFromArray(
int
[] array,
int
index)
{
try
{
return
array[index];
}
catch
(System.IndexOutOfRangeException ex)
{
System.ArgumentException argEx =
new
System.ArgumentException(
"Index is out of range"
,
"index"
, ex);
throw
argEx;
}
}
異常包含一個名為 StackTrace 的屬性。此字符串包含當前調用堆棧上的方法的名稱,以及為每個方法引發異常的位置(文件名和行號)。 StackTrace 對象由公共語言運行時 (CLR) 從 throw 語句點開始自動創建,因此必須從堆棧跟蹤的開始點引發異常。
所有異常都包含一個名為 Message 的屬性。應該設置此字符串來解釋發生異常的原因。注意,不應將安全敏感信息放在消息文本中。除 Message 之外,ArgumentException 還包含一個名為 ParamName 的屬性,應將該屬性設置為導致引發異常的參數的名稱。對於屬性設置器,ParamName 應設置為 value。
公共的受保護方法應在其無法完成預期功能時引發異常。引發的異常類應該是符合錯誤條件的最確切的可用異常。這些異常應編寫為類功能的一部分,派生類或對原始類的更新應保留相同的行為,以實現向後兼容性。
引發異常時要避免的情況
下表確定了在引發異常時要避免的做法:
定義異常類
程序可以引發 System 命名空間中的預定義異常類(前面注明的情況除外),或通過從 Exception 派生來創建它們自己的異常類。派生類至少應定義四個構造函數:一個是默認構造函數,一個用來設置消息屬性,一個用來設置 Message 屬性和 InnerException 屬性。第四個構造函數用於序列化異常。新異常類應該可序列化。例如:
? 1 2 3 4 5 6 7 8 9 10 11public
class
InvalidDepartmentException : System.Exception
{
public
InvalidDepartmentException() :
base
() { }
public
InvalidDepartmentException(
string
message) :
base
(message) { }
public
InvalidDepartmentException(
string
message, System.Exception inner) :
base
(message, inner) { }
// A constructor is needed for serialization when an
// exception propagates from a remoting server to the clIEnt.
protected
InvalidDepartmentException(System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) { }
}
僅當新屬性提供的數據有助於解決異常時,才應將其添加到異常類。如果向派生的異常類添加了新屬性,則應重寫 ToString() 以返回添加的信息。
異常處理
C# 程序員可使用 try 塊對可能受異常影響的代碼進行分區。關聯的 catch 塊用於處理任何結果異常。一個包含代碼的 finally 塊,無論 try 塊中是否引發異常(例如,釋放在 try 塊中分配的資源),這些代碼都會運行。一個 try 塊需要一個或多個關聯的 catch 塊或一個 finally 塊,或兩者。
以下示例給出了一個 try-catch 語句,一個 try-finally 語句,和一個 try-catch-finally 語句。
try
{
// Code to try goes here.
}
catch
(SomeSpecificException ex)
{
// Code to handle the exception goes here.
// Only catch exceptions that you know how to handle.
// Never catch base class System.Exception without
// rethrowing it at the end of the catch block.
}
try
{
// Code to try goes here.
}
finally
{
// Code to execute after the try block goes here.
}
try
{
// Code to try goes here.
}
catch
(SomeSpecificException ex)
{
// Code to handle the exception goes here.
}
finally
{
// Code to execute after the try (and possibly catch) blocks
// goes here.
}
不帶有 catch 或 finally 塊的 try 塊將導致編譯器錯誤。
Catch 塊
catch 塊可以指定要捕捉的異常的該類型。類型規范稱為“異常篩選器”。異常類型應從 Exception 派生出來。一般而言,不會將 Exception 指定為異常篩選器,除非您了解如何處理 try 塊中可能引發的所有異常,或者您在 catch 塊中包括了 throw 語句。
具有不同異常篩選器的多個 catch 塊可以串聯在一起。多個 catch 數據塊的計算順序是在代碼中從頂部到底部,但是,對於所引發的每個異常,都只執行一個 catch 數據塊。與指定的准確類型或其基類最為匹配的第一個 catch 塊被執行。如果 catch 塊沒有指定匹配異常篩選器,則 catch 塊就不具有選定的篩選器(如果語句有的話)。需要將帶有最具體的(即派生程度最高的)異常類的 catch 塊放在最前面。
當下列條件為真時,應該捕捉異常:
對引發異常的原因有具體的了解,並可實現特定的恢復,例如,在捕獲 FileNotFoundException 對象時提示用戶輸入新的文件名。
可以新建一個更具體的異常並引發該異常。
int
GetInt(
int
[] array,
int
index)
{
try
{
return
array[index];
}
catch
(System.IndexOutOfRangeException e)
{
throw
new
System.ArgumentOutOfRangeException(
"Parameter index is out of range."
);
}
}
希望在將異常傳遞出去進行額外處理前部分地處理異常。在下面的示例中,catch 塊用於在再次引發異常之前,向錯誤日志添加條目。
? 1 2 3 4 5 6 7 8 9 10 11try
{
// Try to Access a resource.
}
catch
(System.UnauthorizedAccessException e)
{
// Call a custom error logging procedure.
LogError(e);
// Re-throw the error.
throw
;
}
Finally 塊
可以使用 finally 塊清理在 try 塊中執行的操作。如果存在,finally 塊將在最後執行,在 try 塊和任何匹配 catch 的塊之後執行。不管是否引發異常或者是否找到與異常類型匹配的 catch 塊,finally 始終運行。
可以使用 finally 塊釋放資源(如文件流、數據庫連接和圖形句柄),而不用等待由運行時中的垃圾回收器來完成對象。
在下面的示例中,使用 finally 塊關閉在 try 塊中打開的文件。注意,在關閉文件之前要檢查該文件句柄的狀態。如果 try 塊無法打開文件,則文件句柄仍具有值 null,並且 finally 塊不會嘗試關閉它。或者,如果在 try 塊中成功打開該文件,則 finally 塊將關閉打開的文件。
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15System.IO.FileStream file =
null
;
System.IO.FileInfo fileinfo =
new
System.IO.FileInfo(
"C:\\file.txt"
);
try
{
file = fileinfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Check for null because OpenWrite might have failed.
if
(file !=
null
)
{
file.Close();
}
}