熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

C#的異常處理

2022-06-13   來源: .NET編程 

  通用語言運行時(CLR)具有的一個很大的優勢為異常處理是跨語言被標准化的一個在C#中所引發的異常可以在Visual Basic客戶中得到處理不再有 HRESULTs 或者 ISupportErrorInfo 接口盡管跨語言異常處理的覆蓋面很廣但這一章完全集中討論C#異常處理你稍為改變編譯器的溢出處理行為接著有趣的事情就開始了你處理了該異常要增加更多的手段隨後引發你所創建的異常

   校驗(checked)和非校驗(unchecked)語句

  當你執行運算時有可能會發生計算結果超出結果變量數據類型的有效范圍這種情況被稱為溢出依據不同的編程語言你將被以某種方式通知——或者根本就沒有被通知(C++程序員聽起來熟悉嗎?)那麼C#如何處理溢出的呢? 要找出其默認行為請看我在這本書前面提到的階乘的例子(為了方便其見前面的例子再次在清單 中給出)

  清單 計算一個數的階乘

:using System;
:
: class Factorial
: {
: public static void Main(string[] args)
: {
: long nFactorial = ;
: long nComputeTo = IntParse(args[]);
:
: long nCurDig = ;
: for (nCurDig=;nCurDig <= nComputeTo; nCurDig++)
: nFactorial *= nCurDig;
:
: ConsoleWriteLine({}! is {}nComputeTo
nFactorial);
: }
: }

  當你象這樣使用命令行執行程序時factorial 結果為什麼也沒有發生因此設想C#默默地處理溢出情況而不明確地警告你是安全的通過給整個應用程序(經編譯器開關)或於語句級允許溢出校驗你就可以改變這種行為以下兩節分別解決一種方案

   給溢出校驗設置編譯器

  如果你想給整個應用程序控制溢出校驗C#編譯器設置選擇是正是你所要找的默認地溢出校驗是禁用的要明確地要求它運行以下編譯器命令csc factorialcs /checked+

  現在當你用參數執行應用程序時CLR通知你溢出異常(見圖  允許了溢出異常階乘代碼產生了一個異常  

  按OK鍵離開對話框揭示了異常信息

Exception occurred: SystemOverflowException at FactorialMain(SystemString[])  

  現在你了解了溢出條件引發了一個 SystemOverflowException異常下一節在我們完成語法校驗之後如何捕獲並處理所出現的異常?

   語法溢出校驗

  如果你不想給整個應用程序允許溢出校驗僅給某些代碼段允許校驗你可能會很舒適對於這種場合你可能象清單中顯示的那樣使用校驗語句

  清單   階乘計算中的溢出校驗

: using System;
:
: class Factorial
: {
: public static void Main(string[] args)
: {
: long nFactorial = ;
: long nComputeTo = IntParse(args[]);
:
: long nCurDig = ;
:
: for (nCurDig=;nCurDig <= nComputeTo; nCurDig++)
: checked { nFactorial *= nCurDig; }
:
: ConsoleWriteLine({}! is {}nComputeTo nFactorial);
: }
: }  

  甚至就如你運用標志 checked編譯了該代碼在第行中溢出校驗仍然會對乘法實現檢查錯誤信息保持一致  顯示相反行為的語句是非校驗(unchecked )甚至如果允許了溢出校驗(給編譯器加上checked+標志)被unchecked 語句所括住的代碼也將不會引發溢出異常

unchecked
{
nFactorial *= nCurDig;
}

    異常處理語句

  既然你知道了如何產生一個異常(你會發現更多的方法相信我)仍然存在如何處理它的問題如果你是一個 C++ WIN 程序員肯定熟悉SEH(結構異常處理)你將從中找到安慰C#中的命令幾乎是相同的而且它們也以相似的方式運作The following three sections introduce C#s exceptionhandling statements:以下三節介紹了C#的異常處理語句

  用 trycatch 捕獲異常
  用tryfinally 清除異常
  用trycatchfinally 處理所有的異常

    使用 try 和 catch捕獲異常

  你肯定會對一件事非常感興趣——不要提示給用戶那令人討厭的異常消息以便你的應用程序繼續執行要這樣你必須捕獲(處理)該異常

  這樣使用的語句是try 和 catchtry包含可能會產生異常的語句而catch處理一個異常如果有異常存在的話清單 用try 和 catch為OverflowException 實現異常處理清單 捕獲由Factorial Calculation引發的OverflowException 異常

: using System;
:
: class Factorial
: {
: public static void Main(string[] args)
: {
: long nFactorial = nCurDig=;
: long nComputeTo = IntParse(args[]);
:
: try
: {
: checked
: {
: for (;nCurDig <= nComputeTo; nCurDig++)
: nFactorial *= nCurDig;
: }
: }
: catch (OverflowException oe)
: {
: ConsoleWriteLine(Computing {} caused an overflow exception nComputeTo);
: return;
: }
:
: ConsoleWriteLine({}! is {}nComputeTo nFactorial);
: }
: }

  為了說明清楚我擴展了某些代碼段而且我也保證異常是由checked 語句產生的甚至當你忘記了編譯器設置時
正如你所見異常處理並不麻煩你所有要做的是在try語句中包含容易產生異常的代碼接著捕獲異常該異常在這個例子中是OverflowException類型無論一個異常什麼時候被引發在catch段裡的代碼會注意進行適當的處理

  如果你不事先知道哪一種異常會被預期而仍然想處於安全狀態簡單地忽略異常的類型

try
{

}
catch
{

}

  但是通過這個途徑你不能獲得對異常對象的訪問而該對象含有重要的出錯信息一般化異常處理代碼象這樣try
{

}
catch(SystemException e)
{

}

  注意你不能用ref或out 修飾符傳遞 e 對象給一個方法也不能賦給它一個不同的值 使用 try 和 finally 清除異常如果你更關心清除而不是錯誤處理 try 和 finally 會獲得你的喜歡它不僅抑制了出錯消息而且所有包含在 finally 塊中的代碼在異常被引發後仍然會被執行盡管程序不正常終止但你還可以為用戶獲取一條消息如清單 所示

  清單 在finally 語句中處理異常

: using System;
:
: class Factorial
: {
: public static void Main(string[] args)
: {
: long nFactorial = nCurDig=;
: long nComputeTo = IntParse(args[]);
: bool bAllFine = false;
:
: try
: {
: checked
: {
: for (;nCurDig <= nComputeTo; nCurDig++)
: nFactorial *= nCurDig;
: }
: bAllFine = true;
: }
: finally
: {
: if (!bAllFine)
: ConsoleWriteLine(Computing {} caused an overflow exception nComputeTo);
: else
: ConsoleWriteLine({}! is {}nComputeTo nFactorial);
: }
: }
: }

  通過檢測該代碼你可能會猜到即使沒有引發異常處理finally也會被執行這是真的——在finally中的代碼總是會被執行的不管是否具有異常條件為了舉例說明如何在兩種情況下提供一些有意義的信息給用戶 我引進了新變量bAllFinebAllFine告訴finally 語段它是否是因為一個異常或者僅是因為計算的順利完成而被調用

  作為一個習慣了SEH程序員你可能會想是否有一個與__leave 語句等價的語句該語句在C++中很管用如果你還不了解在C++中的__leave 語句是用來提前終止 try 語段中的執行代碼並立即跳轉到finally 語段 壞消息 C# 中沒有__leave 語句但是在清單 中的代碼演示了一個你可以實現的方案清單 從 try語句 跳轉到finally 語


: using System;
:
: class JumpTest
: {
: public static void Main()
: {
: try
: {
: ConsoleWriteLine(try);
: goto __leave;
: }
: finally
: {
: ConsoleWriteLine(finally);
: }
:
: __leave:
: ConsoleWriteLine(__leave);
: }
: }

  當這個應用程序運行時輸出結果為tryfinally

  leave一個 goto 語句不能退出 一個finally 語段甚至把 goto 語句放在 try 語句 段中還是會立即返回控制到 finally 語段因此goto 只是離開了 try 語段並跳轉到finally 語段直到 finally 中的代碼完成運行後才能到達__leave 標簽按這種方式你可以模仿在SEH中使用的的__leave 語句順便地你可能懷疑goto 語句被忽略了因為它是try 語句中的最後一條語句並且控制自動地轉移到了 finally 為了證明不是這樣試把goto 語句放到ConsoleWriteLine 方法調用之前盡管由於不可到達代碼你得到了編譯器的警告但是你將看到goto語句實際上被執行了且沒有為 try 字符串產生的輸出

   使用trycatchfinally處理所有異常應用程序最有可能的途徑是合並前面兩種錯誤處理技術——捕獲錯誤清除並繼續執行應用程序所有你要做的是在出錯處理代碼中使用 try catch 和 finally語句清單 顯示了處理
零除錯誤的途徑

  清單 實現多個catch 語句

: using System;
:
: class CatchIT
: {
: public static void Main()
: {
: try
: {
: int nTheZero = ;
: int nResult = / nTheZero;
: }
: catch(DivideByZeroException divEx)
: {
: ConsoleWriteLine(divide by zero occurred!);
: }
: catch(Exception Ex)
: {
: ConsoleWriteLine(some other exception);
: }
: finally
: {
: }
: }
: }

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

: try
: {
: int nTheZero = ;
: int nResult = / nTheZero;
: }
: catch(Exception Ex)
: {
: ConsoleWriteLine(exception + ExToString());
: }
: catch(DivideByZeroException divEx)
: {
: ConsoleWriteLine(never going to see that);
: }

  編譯器將捕獲到一個小錯誤並類似這樣報告該錯誤

wrongcatchcs(): error CS: A previous catch
clause already
catches all exceptions of this or a super type (SystemException)

  最後我必須告發CLR異常與SEH相比時的一個缺點(或差別)沒有 EXCEPTION_CONTINUE_EXECUTION標識符的等價物它在SEH異常過濾器中很有用基本上EXCEPTION_CONTINUE_EXECUTION 允許你重新執行負責異常的代碼片段在重新執行之前你有機會更改變量等我個人特別喜歡的技術為使用訪問違例異常按需要實施內存分配

   引發異常

  當你必須捕獲異常時其他人首先必須首先能夠引發異常而且不僅其他人能夠引發你也可以負責引發其相當簡單throw new ArgumentException(Argument cant be );

  你所需要的是throw 語句和一個適當的異常類我已經從表提供的清單中選出一個異常給這個例子

  表 Runtime提供的標准異常

 異常類型  描述  Exception  所有異常對象的基類  SystemException  運行時產生的所有錯誤的基類  IndexOutOfRangeException  當一個數組的下標超出范圍時運行時引發  NullReferenceException   當一個空對象被引用時運行時引發  InvalidOperationException  當對方法的調用對對象的當前狀態無效時由某些方法引發   ArgumentException  所有參數異常的基類  ArgumentNullException  在參數為空(不允許)的情況下由方法引發  ArgumentOutOfRangeException  當參數不在一個給定范圍之內時由方法引發  InteropException  目標在或發生在CLR外面環境中的異常的基類  ComException  包含COM 類的HRESULT信息的異常

  SEHException 封裝win 結構異常處理信息的異常然而在catch語句的內部你已經有了隨意處置的異常就不必創建一個新異常可能在表 中的異常沒有一個符合你特殊的要求——為什麼不創建一個新的異常?在即將要學到小節中都涉及到這兩個話題 重新引發異常當處於一個catch 語句的內部時你可能決定引發一個目前正在再度處理的異常留下進一步的處理給一些外部的trycatch 語句該方法的例子如 清單所示

  清單 重新引發一個異常

: try
: {
: checked
: {
: for (;nCurDig <= nComputeTo; nCurDig++)
: nFactorial *= nCurDig;
: }
: }
: catch (OverflowException oe)
: {
: ConsoleWriteLine(Computing {} caused an
overflow exception nComputeTo);
: throw;
: }

  注意我不必規定所聲明的異常變量盡管它是可選的但你也可以這樣寫

throw oe;

  現在有時還必須留意這個異常 創建自己的異常類盡管建議使用預定義的異常類但對於實際場合創建自己的異常類可能會方便創建自己的異常類允許你的異常類的使用者根據該異常類采取不同的手段
在清單 中出現的異常類 MyImportantException遵循兩個規則第一它用Exception結束類名第二它實現了所有三個被推薦的通用結構你也應該遵守這些規則

  清單 實現自己的異常類

MyImportantException
: using System;
:
: public class MyImportantException:Exception
: {
: public MyImportantException()
: :base() {}
:
: public MyImportantException(string message)
: :base(message) {}
:
: public MyImportantException(string message Exception inner)
: :base(messageinner) {}
: }
:
: public class ExceptionTestApp
: {
: public static void TestThrow()
: {
: throw new MyImportantException(something bad has happened);
: }
:
: public static void Main()
: {
: try
: {
: ExceptionTestAppTestThrow();
: }
: catch (Exception e)
: {
: ConsoleWriteLine(e);
: }
: }
: }

  正如你所看到的MyImportantException 異常類不能實現任何特殊的功能但它完全基於SystemException類程序的剩余部分測試新的異常類給SystemException 類使用一個catch 語句如果沒有特殊的實現而只是給MyImportantException定義了三個構造函數創建它又有什麼意義呢?它是一個重要的類型——你可以在catch語句中使
用它代替更為普通的異常類可能引發你的新異常的客戶代碼可以按規定的catch代碼發揮作用當使用自己的名字空間編寫一個類庫時也要把異常放到該名字空間盡管它並沒有出現在這個例子中你還是應該使用適當的屬性為擴展了的錯誤信息擴充你的異常類 異常處理的不要作為最後的忠告之語這裡是對異常引發和處理所要做
和不要做的清單

  當引發異常時要提供有意義的文本
  要引發異常僅當條件是真正異常也就是當一個正常的返回值不滿足時
  如果你的方法或屬性被傳遞一個壞參數要引發一個ArgumentException異常
  當調用操作不適合對象的當前狀態時要引發一個 InvalidOperationException異常
  要引發最適合的異常
  要使用鏈接異常它們允許你跟蹤異常樹
  不要為正常或預期的錯誤使用異常
  不要為流程的正常控制使用異常
  不要在方法中引發 NullReferenceException或IndexOutOfRangeException異常

   小結

  這一章由介紹溢出校驗開始你可以使用編譯器開關(默認是關)使整個應用程序允許或禁止溢出校驗如果需要微調控制你可以使用校驗和非校驗語句它允許你使用或不使用溢出校驗來執行一段代碼盡管沒有給應用程序設置開關

  當發生溢出時一個異常就被引發了如何處理異常取決於你我提出了各種途徑包括你最有可能貫穿整個應用程序使用的trycatch 和finally 語句在伴隨的多個例子中你學到了它與WIN結構異常處理(SEH)的差別異常處理是給類的用戶 然而如果你負責創建新的類就可以引發異常有多種選擇引發早已捕獲的異常引發存在的框架異常或者按規定的實際目標創建新的異常類最後你需要閱讀引發和處理異常的各種不要


From:http://tw.wingwit.com/Article/program/net/201311/15514.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.