目錄:數據庫
20.1 定義「異常」編程
20.2 異常處理機制數組
20.3 System.Exception類閉包
20.4 FCL定義的異常類異步
20.5 拋出異常編程語言
20.6 定義本身的異常類異步編程
20.7 用可靠性換取開發效率性能
20.8 設計規範和最佳實踐測試
20.9 未處理的異常線程
20.10 對異常進行調試
20.11 異常處理的性能問題
20.12 約束執行區域(CLR)
20.13 代碼協定
異常指成員沒有完成它的名稱所宣稱的行動。
經過異常處理返回錯誤報告。
2.1 try塊:若是代碼須要執行通常性的資源清理操做,須要從異常中恢復,或者二者都須要,就能夠放到try塊中。負責清理的代碼應放到一個finally塊中。try塊還可包含也許會拋出異常的代碼。負責異常恢復的代碼應放到一個或多個catch塊中。
2.2 catch塊:catch塊包含的是響應一個異常須要執行的代碼。catch關鍵字後的圓括號中的表達式稱爲捕捉類型。C#要求捕捉類型必須是System.Exception或者它的派生類型。
在catch塊末尾,有三種選擇:
1.重新拋出相同的異常,向調用棧高一層的代碼通知該異常的發生。
2.拋出一個不一樣的異常,向調用棧高一層的代碼提供更豐富的異常信息。
3.讓線程從catch塊的底部退出。
2.3 finally塊:finally塊包含的是保證會執行的代碼。通常在finally塊中執行try塊的行動所要求的資源清理操做。
CLR容許異常拋出任何類型的實例——從Int32到String均可以。可是,Microsoft決定部強迫全部編程語言都拋出和捕捉任意類型的異常。所以,他們定義了System.Exception類型,並規定全部CLS相容的編程語言都必須能拋出和捕捉派生自該類型的異常。
屬性名稱 | 訪問 | 類型 | 說明 |
Message | 只讀 | String | 包含輔助性文字說明,指出拋出異常的緣由。 |
Data | 只讀 | IDictionary | 引入一個「鍵/值對」集合。一般,代碼在拋出異常前在該程序集合中添加記錄項;捕捉異常的代碼可在異常恢復中查詢記錄項並利用其中的信息。 |
Source | 讀/寫 | String | 包含生成異常的程序集的名稱 |
StackTrace | 只讀 | String | 包含拋出異常以前調用過的全部方法的名稱和簽名,該屬性對調試頗有用。 |
TargetSite | 只讀 | String | 包含拋出異常的方法 |
HelpLink | 只讀 | String | 包含幫助用戶理解異常的一個文檔的URL |
InnerException | 只讀 | Exception | 若是當前異常是在處理一個異常時拋出的,該屬性就指出上一個異常時什麼。 |
HResult | 讀/寫 | Int32 | 跨越託管和本機代碼邊界時使用的一個32位值。 |
拋出異常時,CLR會重置異常起點;也就是說,CLR只記錄最新的異常對象拋出位置。
實現本身得方法時,若是方法沒法完成方法名所指明得任務,就應拋出一個異常。
從Excetion派生的全部類型都應該時可序列化的,使它們能穿越AppDomain邊界或者寫入日誌/數據庫。
面向對象編程極大提高了開發人員的開發效率。開發效率的提高很大一部分來自可組合性,它使代碼容易編寫,閱讀和維護。
除了代碼的可組合性,開發效率的提高還來自編譯器提供的各類好用的功能。
調用方法時插入可選參數
對值類型的實例進行裝箱
構造/初始化參數數組
綁定到dynamic變量/表達式的成員
綁定到擴展方法。
綁定/調用重載的操做符(方法)
構造委託對象。
在調用泛型方法,聲明局部變量和使用lambda表達式時推斷類型。
爲lambda表達式和迭代器定義/構造閉包類。
定義/構造/初始化匿名類型及其實例
重寫代碼來支持LINQ查詢表達式和表達式樹
另外,CLR自己也提供大量輔助來進一步簡化編程:
調用虛方法和接口方法。
加載程序集並對方法進行JIT編譯,會拋出異常。
訪問MarshalByRefObject派生類型的對象時穿越AppDomain邊界。
調用Thread.Abort或AppDomain.Unload時形成線程拋出ThreadAbortException。
垃圾回收以後,在回收對象的內存以前調用Finalize方法。
使用泛型類型時,在Loader堆中建立類型對象。
拋出各類異常。
爲了緩解對狀態的破壞,能夠:
執行catch或finally塊中的代碼時,CLR不容許線程終止。
能夠用System.Diagnostics.Contracts.Contract類向方法應用代碼協定
可使用約束執行區,它能消除CLR的某些不去肯定性。
取決於狀態存在於何處,可利用事務來確保狀態要麼都修改,要麼都不修改。
將本身的方法設計得更明確。
8.1 善用finally塊
確保清理很重要,因此編程語言提供了一些構造來簡化代碼編寫:
使用lock語句時,鎖在finally塊中釋放。
使用using語句時,在finally塊中調用對象的Dispose方法。
使用foreach語句時,在finally塊中調用IEnumerator對象的Dispose方法。
定義析構器方法時,在finally塊中調用基類的Finalize方法。
使用這些構造時,編譯器將你寫的代碼放到try塊內部,並將清理代碼放到finally塊中。
8.2 不要什麼都捕捉
應用程序代碼拋出異常,應用程序的另外一部分可能預期要捕捉該異常。因此,不要寫「大小通吃」的類型,悄悄地吞噬異常,而是應該容許異常在調用棧中向上移動,讓應用程序代碼針對性地處理它。
若是異常未獲得處理,CLR會終止進程。也能夠在一個線程中捕捉異常,在另外一個線程中從新拋出異常。爲此提供支持的是異步編程模型。
8.3 得體地從異常中恢復
捕捉具體異常時,應充分掌握在何時會拋出異常,並知道從捕捉的異常類型派生出了哪些類型。
8.4 發生不可恢復的異常時回滾部分完成的造做——維持狀態
8.5 隱藏實現細節來維持協定
異常拋出時,CLR在調用棧中向上查找與拋出的異常對象的類型匹配的catch塊。沒有任何catch塊匹配拋出的異常類型,就發生一個未處理的異常。CLR檢測到進程中的任何未處理的異常,都會終止進程。未處理異常代表應用程序遇到了未預料到的狀況,並認爲這是應用程序的真正bug。
異常處理的代價:
非託管C++編譯器必須生成代碼來跟蹤哪些對象被成功構造。編譯器還必須生成代碼,以便在一個異常被捕捉到的時候,調用每一個已構造的對象的析構器。
託管編譯器就要輕鬆得多,由於託管對象在託管堆中分配,而託管堆受垃圾回收器監視。如對象成功構造,並且拋出了異常,垃圾回收器最終會釋放對象得內存。
在CLR中,咱們有包含了狀態的AppDomain。AppDomain卸載時,它的全部狀態都會卸載。全部,若是AppDomain中的一個線程遭遇未處理的異常,能夠在不終止整個進程的狀況下卸載AppDomain。
根據定義,CER是必須對錯誤有適應力的代碼塊。因爲AppDomain可能被卸載,形成它的狀態被銷燬,因此通常用CER處理由多個AppDomain或進程共享的狀態。若是要在拋出了非預期的異常時維護狀態,CER就很是有用。有時將這些異常稱爲異步異常。
代碼協定提供了直接在代碼中聲明代碼設計決策的一種方式:
前條件:通常用於對實參進行驗證。
後條件:方法由於一次普通的返回或者拋出異常而終止時,對狀態進行驗證。
對象不變性:在對象整個生命期內,確保對象的字段的良好狀態。
代碼協定有利於代碼的使用,理解,進化,測試,文檔和早期錯誤檢測。可將前條件,後條件和對象不變性想象爲方法簽名的一部分。