06 管理錯誤和異常

1.處理錯誤

生活並非總是一帆風順。輪胎可能扎破,電池可能耗盡,螺絲起子並非總在老地方,應用程序的用戶可能進行了出乎意料的操作。在計算機世界裏,磁盤可能出故障,編寫不當的程序可能影響機器上運行的其他應用程序(比如由於程序bug造成耗盡所有內存),無線網絡可能在最不恰當的時刻斷開連接,甚至一些自然現象(比如附近的一次閃電)也會造成電源或者網絡故障。錯誤可能在程序運行的任何階段發生,其中許多都不是程序本身的問題。

人們多年來爲此研發了大量機制。早期系統(如UNIX)採用的典型方案要求在每次方法出錯時都由操作系統設置一個特殊全局變量。每次調用方法後都檢查全局變量,判斷方法是否成功。和大多數面向對象編程語言一樣,C#沒有使用這種痛苦的、折磨人的方式處理錯誤。相反,它使用異常,爲了寫健壯的C#應用程序,必須很好地掌握異常。

 

2.嘗試執行代碼和捕捉異常

C#中利用異常和異常處理程序,可以很容易地區分實現程序主邏輯地代碼與處理錯誤地代碼。爲了寫支持異常處理地應用程序,要做下面兩件事情。

(1)代碼放到try塊中(try是C#關鍵字)。代碼運行時,會嘗試執行try塊內的所有語句。如果沒有任何語句產生異常,就會正常全部運行完成。但一旦出現異常,就跳出try塊,進入一個catch處理程序中執行。

(2)緊接着try塊寫一個或多個catch處理程序(catch也是C#關鍵字)來處理可能發生的錯誤。每個catch處理程序都捕捉並處理特定類型的異常,可在try塊後面寫多個catch處理程序。try塊中的任何語句造成錯誤,“運行時”都會生成並拋出異常。然後,“運行時”檢查try塊之後的catch處理程序,將控制權移交給匹配的處理程序。

 

舉例:下面將字符串轉換成整型,如果字符串包含了無效字符,int.Prase方法拋出FormatException異常,並將控制權移交給對應的catch處理程序。catch處理程序結束後,程序從整個try/catch塊之後的第一個語句繼續。注意,如果沒有和異常對應的處理程序,就說明異常未處理(稍後會討論這種情況)

void parseInt(string str)

{

    try{

        int number = int.Parse(str);

    }

    catch(FormatException fEx)

    {

        //處理異常

        ……

    }

}

catch處理程序採用與方法參數相似的語法指定要捕捉的異常。在前例中,一旦拋出FormatException異常,fEx變量就會被填充一個對象,其中包含了異常的細節。FormatException類型提供大量屬性供檢查造成異常的確切原因。不少屬性是所有異常通用的。例如,Message屬性包含錯誤的文本描述。

 

2.1未處理的異常

如果try塊拋出異常,但沒有對應的catch處理程序,那麼會發生什麼?在上面的例子中,str可能是一個整數,但如果該整數超出了C#允許的整數範圍(例如2147483648)。在這種情況下,int,Parse語句會拋出OverflowWxception異常,而catch處理程序目前只能捕捉FormatException異常。如果try塊是某個方法的一部分,那個方法將立即退出,並返回它的調用方法。如果它的調用方法有try塊,“運行時”會嘗試定位try塊之後的一個匹配catch處理程序並執行。如果調用方法沒有try塊,或者沒有找到匹配的catch處理程序,調用方法退出,返回它的更上一層的調用方法……以此類推。如果最後找到了匹配的catch處理程序,就運行它,然後從捕獲(到異常的)方法的catch處理程序之後的第一個語句繼續執行。

由內向外遍歷了所有調用方法之後,如果還是找不到匹配的catch處理程序,整個程序終止,報告發生了未處理的異常。

 

2.2使用多個catch處理異常

void parseInt(string str)

{

    try{

        int number = int.Parse(str);

    }

    catch(FormatException fEx)

    {

        //處理異常

        ……

    }

    catch(OverflowException oEx)

    {

        //處理異常

        ……

    }

}

2.3捕捉多個異常

異常用繼承層次結構進行組織。這個繼承層次結構由多個“家族”構成(後面將會詳細討論繼承)。FormatException和OverflowException異常都屬於SystemException家族。該家族還包含其他許多異常。SystemException本身又是Exception家族的成員,而Exception是所有異常的“老祖宗”。捕捉Exception相當於捕捉所有可能發生的異常。

 

以下代碼演示如何捕捉所有可能的異常:

void parseInt(string str)

{

    try{

        int number = int.Parse(str);

    }

    catch(Exception fEx)

    {

        //處理異常

        ……

    }

}

2.4異常過濾器

允許指定catch處理程序的額外使用條件。這些條件採用的形式是when關鍵字加布爾表達式,如下例所示:

catch(Exception ex)when(ex.GetType() != typeof(System.OutOfMemoryException))

{

    //處理之前未捕捉的除OutOfMemoryException之外的所有異常

}

3.使用checked和unchecked整數運算

問題:在當前值已經是2147483647的一個int上加1會發生什麼?

C#編譯器默認允許悄悄溢出。換言之,將得到一個錯誤答案(結果是:-2147483648)

 

3.1編寫checked語句

checked語句是以checked關鍵字開頭的代碼塊。checked語句中的任何整數運算溢出都拋出OverflowException異常,如下例所示:

使用checked(checked語句中的任何整數運算溢出都會拋出OverflowException異常)

int number = int.MaxValue;

checked

{

      int willThrow = number ++;

      Console.WriteLine("永遠都執行不到這裏");

}

使用unchecked語句(不拋異常)

int number = int.MaxValue;

unchecked

{

      int willThrow = number ++;

      Console.WriteLine("會執行到這裏");

}

3.2編寫checked表達式

還可以使用checked和unchecked關鍵字控制單獨整數表達式的溢出檢查

int wontThrow = unchecked(int.MaxValue + 1);   //不拋出異常

int willThrow = checked(int.MaxValue + 1);   //拋出異常

4.拋出異常&聲明異常

我們可以不在當前方法處理異常,而是將產生的異常往外拋,讓調用當前方法的處理該異常,以此類推

比如:

void parseInt(string str)throws FormatException,OverflowException

{

    try{

        int number = int.Parse(str);

    }

    catch(FormatException fEx)

    {

        throw new FormatException("格式異常");

    }

    catch(OverflowException oEx)

    {

        throw new OverflowException("越界異常");

    }


}

throw是拋出異常,throws是方法聲明可能產生的異常

 

5.使用finally塊

之前說過,當catch處理程序運行完畢,會從整個try/catch塊之後的語句繼續,而不是從拋出異常的語句之後繼續。

如果程序運行到一半,出了異常,一些之前語句獲取的資源得不到釋放,就有可能出現問題,所以我們可以寫一個finally塊,放到其中的語句總是執行(無論是否拋出異常)。finally塊要麼緊接在try塊之後,要麼緊接最後一個catch塊之後。

所以可用以下方案確保reader.Dispose總是得到調用:

TextReader  reader = …;

…

try

{

    string line = reader.ReadLine();

    while(line != null)

    {

        …

        line = reader.ReadLine();

    }

}

finally

{

    if(reader != null)

    {

        reader.Dispose();

    }

}

即使讀取文件時發生異常,finally塊也保證reader.Dispose語句得到執行。之後將會介紹解決該問題的另一個方案(使用using語句)。

 

參考書籍:《Visual C#從入門到精通》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章