C#中的異常處理
[2] C#中的異常處理
通用語言運行時(CLR)具有的一個很大的優勢,異常處理是跨語言被標準化的。一個在C#中所引發的異常可以在Visual Basic客戶中得到處理。不再有 HRESULTs 或者 ISupportErrorInfo 接口。
盡管跨語言異常處理的覆蓋面很廣,你稍為改變編譯器的溢出處理行為,接著有趣的事情就開始了:你處理了該異常。要增加更多的手段,隨后引發你所創建的異常。
1.1 校驗(checked)和非校驗(unchecked)語句
當你執行運算時,有可能會發生計算結果超出結果變量數據類型的有效范圍。這種情況被稱為溢出,依據不同的編程語言,你將被以某種方式通知——或者根本就沒有被通知。
那么,C#如何處理溢出的呢? 要找出其默認行為,請看下面的階乘的例子。
example 1.1 計算一個數的階乘

當你象這樣使用命令行執行程序時
factorial 2000
結果為0,什么也沒有發生。因此,設想C#默默地處理溢出情況而不明確地警告你是安全的。
通過給整個應用程序(經編譯器開關)或于語句級允許溢出校驗,你就可以改變這種行為。以下兩節分別解決一種方
案。
1.1.1 給溢出校驗設置編譯器
如果你想給整個應用程序控制溢出校驗,C#編譯器設置選擇是正是你所要找的。默認地,溢出校驗是禁用的。要明確
地要求它,運行以下編譯器命令:
csc factorial.cs /checked+
現在當你用2000參數執行應用程序時,CLR通知你溢出異常。
允許了溢出異常,階乘代碼產生了一個異常。
按OK鍵離開對話框揭示了異常信息:
Exception occurred: System.OverflowException
at Factorial.Main(System.String[])
現在你了解了溢出條件引發了一個 System.OverflowException異常。
1.1.2 語法溢出校驗
如果你不想給整個應用程序允許溢出校驗,僅給某些代碼段允許校驗,你可能會很舒適。對于這種場合,你可能象清
單1.2中顯示的那樣,使用校驗語句。
example 1.2 階乘計算中的溢出校驗

甚至就如你運用標志 checked-編譯了該代碼,在第13行中,溢出校驗仍然會對乘法實現檢查。錯誤信息保持一致。
顯示相反行為的語句是非校驗(unchecked )。甚至如果允許了溢出校驗(給編譯器加上checked+標志),被
unchecked 語句所括住的代碼也將不會引發溢出異常:
unchecked
{
nFactorial *= nCurDig;
}
1.2 異常處理語句
The following three sections introduce C#'s exception-handling statements:
以下三節介紹了C#的異常處理語句:
。用 try-catch 捕獲異常
。用try-finally 清除異常
。用try-catch-finally 處理所有的異常
1.2.1 使用 try 和 catch捕獲異常
你肯定會對一件事非常感興趣——不要提示給用戶那令人討厭的異常消息,以便你的應用程序繼續執行。要這樣,你
必須捕獲(處理)該異常。
這樣使用的語句是try 和 catch。try包含可能會產生異常的語句,而catch處理一個異常,如果有異常存在的話。
example 1.3 捕獲由Factorial Calculation引發的OverflowException 異常

為了說明清楚,我擴展了某些代碼段,而且我也保證異常是由checked 語句產生的,甚至當你忘記了編譯器設置時。
正如你所見,異常處理并不麻煩。你所有要做的是:在try語句中包含容易產生異常的代碼,接著捕獲異常,該異常在
這個例子中是OverflowException類型。無論一個異常什么時候被引發,在catch段里的代碼會注意進行適當的處理。
如果你不事先知道哪一種異常會被預期,而仍然想處于安全狀態,簡單地忽略異常的類型。
try
{
...
}
catch
{
...
}
但是,通過這個途徑,你不能獲得對異常對象的訪問,而該對象含有重要的出錯信息。一般化異常處理代碼象這樣:
try
{
...
}
catch(System.Exception e)
{
...
}
注意,你不能用ref或out 修飾符傳遞 e 對象給一個方法,也不能賦給它一個不同的值。
1.2.2 使用 try 和 finally 清除異常
如果你更關心清除而不是錯誤處理, try 和 finally 會獲得你的喜歡。它不僅抑制了出錯消息,而且所有包含在
finally 塊中的代碼在異常被引發后仍然會被執行。
盡管程序不正常終止,但你還可以為用戶獲取一條消息,如example 1.4 所示。
example 1.4 在finally 語句中處理異常

通過檢測該代碼,你可能會猜到,即使沒有引發異常處理,finally也會被執行。這是真的——在finally中的代碼總
是會被執行的,不管是否具有異常條件。為了舉例說明如何在兩種情況下提供一些有意義的信息給用戶, 我引進了新變量
bAllFine。bAllFine告訴finally 語段,它是否是因為一個異常或者僅是因為計算的順利完成而被調用。
作為一個習慣了SEH程序員,你可能會想,是否有一個與__leave 語句等價的語句,該語句在C++中很管用。如果你還
不了解,在C++中的__leave 語句是用來提前終止 try 語段中的執行代碼,并立即跳轉到finally 語段 。
壞消息, C# 中沒有__leave 語句。但是,在example 1.5 中的代碼演示了一個你可以實現的方案。
example 1.5 從 try語句 跳轉到finally 語句

當這個應用程序運行時,輸出結果為
try
finally
__leave
一個 goto 語句不能退出 一個finally 語段。甚至把 goto 語句放在 try 語句 段中,還是會立即返回控制到
finally 語段。因此,goto 只是離開了 try 語段并跳轉到finally 語段。直到 finally 中的代碼完成運行后,才能到達
__leave 標簽。按這種方式,你可以模仿在SEH中使用的的__leave 語句。
順便地,你可能懷疑goto 語句被忽略了,因為它是try 語句中的最后一條語句,并且控制自動地轉移到了
finally 。為了證明不是這樣,試把goto 語句放到Console.WriteLine 方法調用之前。盡管由于不可到達代碼你得到了編
譯器的警告,但是你將看到goto語句實際上被執行了,且沒有為 try 字符串產生的輸出。
1.2.3 使用try-catch-finally處理所有異常
應用程序最有可能的途徑是合并前面兩種錯誤處理技術——捕獲錯誤、清除并繼續執行應用程序。所有你要做的是在
出錯處理代碼中使用 try 、catch 和 finally語句。example 1.6 顯示了處理零除錯誤的途徑。
example 1.6 實現多個catch 語句

這個例子的技巧為,它包含了多個catch 語句。第一個捕獲了更可能出現的DivideByZeroException異常,而第二個
catch語句通過捕獲普通異常處理了所有剩下來的異常。
你肯定總是首先捕獲特定的異常,接著是普通的異常。如果你不按這個順序捕獲異常,會發生什么事呢?example 1.7中的
代碼有說明。
example 1.7 順序不適當的 catch 語句

編譯器將捕獲到一個小錯誤,并類似這樣報告該錯誤:
wrongcatch.cs(10,9): error CS0160: A previous catch clause already
catches all exceptions of this or a super type ('System.Exception')
最后,我必須告發CLR異常與SEH相比時的一個缺點(或差別):沒有 EXCEPTION_CONTINUE_EXECUTION標識符的等價
物,它在SEH異常過濾器中很有用。基本上,EXCEPTION_CONTINUE_EXECUTION 允許你重新執行負責異常的代碼片段。在重
新執行之前,你有機會更改變量等。我個人特別喜歡的技術為,使用訪問違例異常,按需要實施內存分配。