異常處理

何時應該拋出異常

當一個類型的行動成員不能正在完整行動任務時,就應該拋出異常通知調用者。c++

*行動成員指類型自己或者類型實例能夠執行的操做,如C#中StringBuilder中定義的Append,Insert等編程

捕捉異常代碼結構
private void DoSomething()
{
    try
    {
        //將可能發生異常的代碼放在這裏
    }
    catch (InvalidOperationException)
    {
        //捕捉到InvalidOperationException異常,對應的處理代碼放在這裏
    }
    catch (IOException)
    {
        //捕捉到IOException異常,對應的處理代碼放在這裏
    }
    catch (Exception)
    {
        //捕捉到除了上述以外的其它異常,對應的處理代碼放在這裏

        //這裏是將異常拋出
        throw;
    }
    finally
    {
        //這裏的代碼老是被執行
    }

    //若是try塊沒有拋出異常或者某個catch捕捉到異常卻沒有拋出就執行如下的代碼,不然如下代碼不執行
}
try塊

try塊中包含的是可能會發生異常的代碼,異常恢復代碼應放在一個或多個catch塊中。針對應用程序安全的恢復某一種異常都須要有一個對應的catch塊。一個try塊至少要關聯一個catch塊或finally塊,單獨一個try塊C#是不容許的,並且這樣也沒有意義。windows

*若是一個try塊中包含執行多個可能拋出同一個異常類型的操做,但不一樣的操做對應的恢復措施不一樣,咱們就應該將這些操做拆分到它本身的try塊中,以保證正確的恢復狀態安全

catch塊

catch塊包含的是響應一個異常須要執行的代碼。一個try塊能夠關聯0個或多個catch塊。若是try塊中的代碼沒有異常發生,CLR永遠不會執行catch塊中的代碼。線程將跳過全部catch塊,直至finally塊(裏面有代碼的話)中的代碼。catch關鍵字後面圓括號中的表達式稱爲捕捉類型,即要捕捉的異常類型。ide

*使用Visual Studio調試catch塊時,能夠在監視窗口中添加特殊的變量名稱$exception來檢查當前拋出異常的對象性能

catch塊檢索順序與注意事項

CLR是自上而下檢索一個匹配的catch塊,因此編程的時候應注意將派生程度最大的異常類型放在頂部,接着是它們的基類,最後纔是System.Exception,若是順序沒有放對,例如將最具體的異常類型放在了最底部的catch塊中,C#編譯器將會發生錯誤,由於這個catch塊沒法被執行到。ui

一旦try塊中的代碼發生異常,而沒有與之匹配的catch塊的話,CLR會去調用棧的更高一層搜索與之匹配的異常類型,若是到了棧的頂部仍是沒有找到,就會發生一個未處理的異常。spa

在catch塊的末尾能夠作的事情
  • 從新拋出相同的異常,向調用棧高一層的代碼通知該異常的發生
  • 拋出一個不一樣的異常,向調用棧高一層的代碼提供更加豐富的異常信息
  • 讓線程從catch塊的底部退出

前兩種技術CLR將回溯調用棧,查找捕捉類型與拋出異常的類型匹配的catch塊並拋出一個異常。
若是選擇讓線程從catch塊的底部退出,將當即執行包含在finally塊中的代碼,完畢後執行緊跟在finally塊以後的語句。若是不存在finally塊,線程將從最後一個catch塊以後的語句開始執行。線程

finally塊

finally塊中的代碼是保證必定會執行的代碼且必定要在全部catch塊的後面,一般包含的是對try塊中的行動所要求的資源清理操做。一個try塊最多隻能關聯一個finally塊。調試

private void ReadFile(string path)
{
    FileStream fs = null;
    try
    {
        fs = new FileStream(path, FileMode.Open);
        //處理文件數據...
    }
    catch (IOException)
    {
        //IOException異常恢復代碼
    }
    finally
    {
        //確保文件關閉
        if (fs != null)
            fs.Close();
    }
}

上述代碼,不管try塊代碼有沒有發生異常,文件都必定會被關閉。若是將關閉文件的代碼放在finally塊語句以後是不正確的,由於若是拋出異常但沒有捕捉到,finally塊以後的語句將永遠不會被執行,直到下一次垃圾回收纔會關閉文件。

*通常狀況下catch塊和finally塊中的代碼應只有一兩行

System.Exception類屬性介紹
  • 只讀屬性Message:String類型,指出拋出異常的緣由
  • 只讀屬性Data:IDictionary類型,代碼會在拋出異常以前在該集合中添加一個記錄項
  • 可讀可寫屬性Source:String類型,包含生成異常的程序集的名稱
  • 只讀屬性StackTrace:String類型,包含拋出異常以前調用過的全部方法的名稱和簽名,有助於調試代碼
  • 只讀屬性TargetSite:MethodBase類型,包含拋出異常的方法
  • 只讀屬性HelpLink:String類型,包含異常文檔的URL,不建議使用
  • 只讀屬性InnerException:Exception類型,一般值爲null,若是當前異常是在處理一個異常時拋出的,那麼該屬性就指出前一個異常是什麼
正確使用異常類
  • 善用finally塊,經常使用於顯示釋放對象,避免資源泄露
  • 維持狀態,發生不可恢復的異常時回滾部分完成的操做
  • 在一個線程中捕捉異常,在另外一個線程中從新拋出異常
  • 合理的從異常中恢復狀態
private string SomeMothods()
{
    var result = "";
    var a = 1;
    var b = 0;
    try
    {
        a /= b;
    }
    catch (DivideByZeroException)
    {

        result = "被除數不能爲0";
    }

    return result;
}
未處理異常

異常拋出時,CLR會在調用棧中向上查找與拋出異常對象的類型匹配的catch塊。若是沒有找到,就會發生一個未處理的異常。當CLR檢測到進程中的任意一個線程有未處理的異常,都會終止進程。發生未處理異常代表程序遇到了未預料的狀況。同時,發生未處理的異常時windows會向事件日誌寫入一條記錄,能夠打開事件查看器查看
image.png

異常設置
可經過調試菜單打開異常設置窗口如圖
image.png

展開Common Languages Runtime Exceptions能夠查看Visual Studio可以識別的異常類型
image.png

若是勾選了異常類型的複選框,調試器就會在拋出該異常的時候中斷,此時CLR不會查找任何與之匹配的catch塊。
若是異常類的複選框沒有勾選,調試器只有在該異常類型未獲得處理時纔會中斷。

經過添加操做還能夠添加自定義的異常類型
image.png

異常處理的性能問題
  • 非託管c++編譯器:必須生成代碼來跟蹤哪些對象被構形成功,編譯器還必須生成代碼用來在一個異常被捕捉到的時候,調用每個已經成功構造的對象的析構器。如此便會在程序中生成大量的bookkeeping代碼,對代碼的大小和執行時間都會形成負面影響。
  • 託管編譯器:因爲託管對象是在託管堆中分配的,而託管堆受到垃圾回收的監視。若是一個對象成功構造而且拋出一個異常,垃圾回收器最終會釋放對象的內存。編譯器無需生成任何bookkeeping代碼來跟蹤成功構造的對象,也無需保證析構器的調用。與託管c++相比,意味着生成的代碼更少,運行要執行的代碼更少,應用程序的性能更好。
相關文章
相關標籤/搜索