新聞中心
【稿件】在 .NET 中異常處理是一個(gè)龐大的模塊,專門用來(lái)處理程序中的已知可捕獲異常,這篇文章我將詳細(xì)講解異常處理的細(xì)節(jié)性的東西,其中包含了異常處理類型、自定義異常處理、多 catch 的異常處理以及異常處理的依賴。

創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站建設(shè)、網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的響水網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
一、異常處理類型
C# 允許我們編寫的代碼拋出從 System.Exception 派生的任何異常類型(這其中包括了間接派生和直接派生)。例如下面的代碼段:
- public class Demo
- {
- public int StringToNumber(string para)
- {
- string[] numberArray={"零","一","二","三"};
- int number = Array.IndexOf(numberArray,(para??throw new ArgumentNullException(nameof(para))));
- if (number <0)
- {
- throw new ArgumentException("參數(shù)值無(wú)法轉(zhuǎn)換為數(shù)字",nameof(para));
- }
- return number;
- }
- }
上述代碼使用了 throw 關(guān)鍵字拋出了異常,并且使用了特定的異常類型說(shuō)明了發(fā)生異常的背景。在代碼中我們只用到了 C# 7.0 的新特性 throw 表達(dá)式 ,在 para 為 null 時(shí)會(huì)拋出 ArgumentNullException 異常,當(dāng) number 的值小于 0 的時(shí)候我們并沒有拋出 Exception 類型的異常,而是拋出了更能明確告知異常原因的 ArgumentException 類型的異常。我們從代碼中可以看到,當(dāng) para 參數(shù)為 null 時(shí)拋出的是 ArgumentNullException 類型的異常而不是 NullReferenceException 類型的異常。對(duì)于這兩個(gè)類型的異常好多開發(fā)人員其實(shí)并不清楚它倆的區(qū)別。其實(shí)它倆的區(qū)別還是很簡(jiǎn)單的, ArgumentNullException 是在錯(cuò)誤的傳遞了空值時(shí)拋出的,如果傳遞的是 非空的無(wú)效參數(shù) 則必須使用 ArgumentException 或者 ArgumentOutOfRangeException 。如果是底層運(yùn)行時(shí)發(fā)現(xiàn)對(duì)象的值為空的時(shí)候才會(huì)拋出 NullReferenceException 類型的異常,這個(gè)異常一般來(lái)說(shuō)開發(fā)人員不能隨意拋出,我們應(yīng)該先判斷參數(shù)是否為空之后再使用參數(shù),如果為空就拋出 ArgumentNullException 異常。
除了 NullReferenceException 異常外,還有五種派生自 System.SystemException 的異常不能自己拋出,只能有運(yùn)行時(shí)拋出,它們分別是 System.StackOverflowException 、 System.OutOfMemoryException 、System.Runtime.InteropServices.COMException 、System.ExecutionEngineException 和 System.Runtime.InteropServices.SEHException 。同樣,開發(fā)人員盡量不在程序代碼中拋出 Exception 和 ApplicationException 異常,因?yàn)樗鼈兯从吵鰜?lái)的異常過(guò)于籠統(tǒng),沒法為異常提供明確的信息。
在實(shí)際項(xiàng)目開發(fā)中有可能會(huì)遇到代碼執(zhí)行到一定程度就會(huì)出現(xiàn)不安全或者無(wú)法恢復(fù)的狀態(tài),這時(shí)代碼大多數(shù)情況下不會(huì)出現(xiàn)異常,因此我們?cè)谶@種情況下就必須調(diào)用 System.Environemnt.FailFast 方法終止程序,這個(gè)方法會(huì)向?qū)嵺`日志寫入一條消息之后馬上終止程序進(jìn)程。 前面的代碼中我們還使用了 nameof 操作符,使用這個(gè)操作符首先是因?yàn)槲覀兛梢岳弥貥?gòu)工具方便的自動(dòng)更改標(biāo)識(shí)符,另外如果參數(shù)名發(fā)生了變化我們能及時(shí)收到編譯錯(cuò)誤。
針對(duì)這一節(jié)的內(nèi)容我來(lái)做一個(gè)簡(jiǎn)單的總結(jié):
-
成員接收到錯(cuò)誤的參數(shù)時(shí)應(yīng)當(dāng)拋出 ArgumentException 異?;蛘咚淖宇愋彤惓#?/p>
-
在拋出 ArgumentException 異?;蛘咦宇愋彤惓r(shí)必須設(shè)置 ParamName 屬性,也就是 nameof;
-
拋出的異常必須能明確表示異常的問(wèn)題;
-
避免在意外獲得空值時(shí)拋出 NullReferenceException 異常;
-
不要拋出 System.SystemException 及其派生的異常;
-
不要拋出 Exception 和 ApplicationException 異常;
-
如果程序出現(xiàn)不安全因素時(shí)必須調(diào)用 System.Environemnt.FailFast 方法來(lái)終止程序的運(yùn)行;
-
要向傳給參數(shù)異常類型的 ParamName 使用 nameof 操作符
Tip:參數(shù)異常類型包括 ArgumentNullException 、ArgumentNullException 、ArgumentOutOfRangeException
二、捕獲異常處理
捕獲異常處理這一節(jié)比較簡(jiǎn)單,主要需要了解并掌握的是多 catch 塊和異常類型的順序問(wèn)題以及 when 子句。
-
多 catch 塊 多個(gè) catch 塊在 C# 中是比較常見的,我們前面一節(jié)說(shuō)過(guò)拋出的異常必須能明確表示異常的問(wèn)題,因此我們可以利用多 catch 塊解決一個(gè)代碼段中有可能出現(xiàn)的多種異常的情況,每個(gè) catch 塊針對(duì)一種異常情況進(jìn)行處理。我們來(lái)看一個(gè)簡(jiǎn)單的代碼段:
- void OpenFile(string filePath)
- {
- try
- {
- //more code
- }
- catch(ArgumentNullException ex)
- {
- //more code
- }
- catch(DirectoryNotFoundException ex)
- {
- //more code
- }
- catch(FileNotFoundException ex)
- {
- //more code
- }
- catch(IOException ex)
- {
- //more code
- }
- catch(Exception ex)
- {
- //more code
- }
- ?
- }
上述代碼中我們一共定義了 5 個(gè) catch 塊,當(dāng)發(fā)生異常時(shí)會(huì)被對(duì)應(yīng)的 catch 塊攔截并處理。這一小節(jié)就這么簡(jiǎn)單,主要是多 catch 塊的使用,下一小節(jié)我將講解 catch 塊最重要的內(nèi)容。
-
異常類型的順序 異常類型的順序是很多初學(xué)者甚至是部分多年的老程序員會(huì)犯的問(wèn)題,我們從前面的代碼中也可以看到 Exception 異常位于最后的位置, IOException 位于倒數(shù)第二的位置,這是因?yàn)?Exception 異常是所有異常的父類,所有的異常都是直接或間接派生自它,而 IOException 又是 DirectoryNotFoundException 和 FileNotFoundException 的父類。根據(jù)異常匹配的順序,C# 會(huì)始終匹配第一個(gè)符合要求的異常,如果將父類異常放在子類異常的前面,那么再代碼出現(xiàn)異常的時(shí)候回直接匹配父類異常的 catch ,不再去匹配后面的子類異常 catch 。
Tip:不管在什么情況下都必須把 Exception 異常作為最后的 catch ,當(dāng)程序中出現(xiàn)的異常沒有匹配任何 catch 塊時(shí)可以被 Exception catch 塊攔截并處理
-
when 子句 從 C# 6.0 開始, catch 塊支持條件表達(dá)式,這樣我們可以不根據(jù)異常類型來(lái)匹配程序中出現(xiàn)的異常。When 子句返回的時(shí)一個(gè)布爾值,當(dāng)返回 true 時(shí) catch 塊才會(huì)執(zhí)行。我們來(lái)看一個(gè)使用 when 子句的例子:
- try
- {
- //more code
- }
- catch(Win32Exception ex) when (ex.NativeErrorCode==42)
- {
- //more code
- }
不過(guò)我們也可以在 catch 塊中使用 if 語(yǔ)句執(zhí)行上面的條件檢查,但是這樣做的話整個(gè) catch 塊的邏輯就變?yōu)橄瘸蔀楫惓L幚沓绦?,再進(jìn)行條件判斷,進(jìn)而造成了在不滿足條件的情況下無(wú)法去執(zhí)行別的符合要求的 catch 塊。如果使用了 when 子句程序就可以先檢查條件,在決定是否執(zhí)行 catch 塊。但是 when 自己也有需要注意的地方,如果 when 子句中拋出了異常,那么這新的異常就會(huì)被忽略并且整個(gè) when 子句返回值將變?yōu)?false 。
-
重新拋出異常 這里在簡(jiǎn)單說(shuō)一下異常的重新拋出,有些開發(fā)人員喜歡在 catch 塊中寫這段語(yǔ)句
throw ex。這段語(yǔ)句存在一個(gè)致命的問(wèn)題,在 catch 塊中這么寫將會(huì)拋出一個(gè)新的異常,那么將會(huì)造成所有的棧信息被更新進(jìn)而丟失最初的棧信息造成難以定位問(wèn)題。因此 C# 開發(fā)團(tuán)隊(duì)設(shè)計(jì)出了可以不指定具體異常的方法,就是在 catch 塊中直接使用 throw 語(yǔ)句。這樣我們就可以判斷當(dāng)前 catch 塊是否可以處理這個(gè)異常,如果不能就講原始棧信息拋出去。
三、常規(guī) catch
C# 要求代碼拋出的任何對(duì)象都必須從 Exception 派生,從 C#2.0 開始,不管是不是從 Exception 派生的所有異常在進(jìn)入程序集之后,都會(huì)被打包成從 Exception 派生的。結(jié)果是捕捉 Exception 的 catch 塊現(xiàn)在可捕捉前面的塊不能捕捉的所有異常。
-
簡(jiǎn)述 C# 還支持常規(guī) catch 塊,即 catch{} ,它的行為和 catch(Exception ex) 塊的行為一樣,唯一不同的是它不具備類型名和變量名。同樣它也必須位于所有 catch 塊的末尾。在代碼中如果同時(shí)存在常規(guī) catch 塊和 catch(Exception ex) 塊編譯器就會(huì)顯示警告,因?yàn)槌绦驎?huì)永遠(yuǎn)匹配 catch(Exception ex) 塊而不去匹配常規(guī) catch 塊。之所以 C# 中出現(xiàn)常規(guī) catch 塊的原因是因?yàn)槿绻绦蛑写嬖谡{(diào)用的別的語(yǔ)言開發(fā)的程序集,并且該程序集在使用過(guò)程中拋出了異常,那么這個(gè)異常是不會(huì)被 catch(Exception ex) 塊所攔截,而是進(jìn)入到未處理狀態(tài),為了避免這個(gè)問(wèn)題 c# 就推出了常規(guī) catch 塊。
Tip:雖然常規(guī) catch 塊具有強(qiáng)大的功能,但是它依然存在一個(gè)問(wèn)題。它不具備一個(gè)可供訪問(wèn)的異常實(shí)例,所以無(wú)法確定異常是無(wú)害的還是有害于程序的。
-
原理 常規(guī) catch 所生成的 CIL 代碼是 catch(object),這就說(shuō)明不管拋出什么類型它都可以捕獲得到。雖然生成的 CIL 代碼是 catch(object),但是我們不能在代碼中直接這么寫。常規(guī) catch 塊無(wú)法捕獲不是派生自 Exception 的異常,因此 C# 在設(shè)計(jì)的時(shí)候?qū)⑺衼?lái)自其他語(yǔ)言的異常都統(tǒng)一設(shè)置為 System.Runtime.InteropServices.SEHException 異常,因此常規(guī) catch 塊既能捕獲繼承自 Exception 的異常,又能捕獲非托管代碼的異常。
四、規(guī)范
異常處理規(guī)范不是由微軟所規(guī)定的,而是開發(fā)人員在千千萬(wàn)萬(wàn)的項(xiàng)目中總結(jié)出來(lái)的,下面我們來(lái)看一下。
-
只捕獲可以處理的異常 通常我們只處理當(dāng)前代碼可以處理的異常,而不能處理的異常將會(huì)拋出去,讓棧中層級(jí)高的調(diào)用者去處理。
-
不隱藏?zé)o法處理的異常 這個(gè)問(wèn)題會(huì)發(fā)生在剛剛從事開發(fā)的人員身上,他們會(huì)捕獲所有異常即不處理也不拋出。這種情況下如果系統(tǒng)出現(xiàn)問(wèn)題那么將逃過(guò)檢測(cè)。
-
少用 Exception 和常規(guī) catch 塊 所有的異常都是繼承自 Exception ,因此使用 Exception 來(lái)處理異常并不是一個(gè)最優(yōu)方法,而且某些異常需要馬上關(guān)閉程序進(jìn)程。
-
避免在調(diào)用棧較低的位置報(bào)告或記錄異常 大部分調(diào)用棧較低的位置無(wú)法完整處理異常,所以只能拋出異常,并且如果在這些位置記錄異常并且再拋出異常會(huì)造成異常的重復(fù)記錄。
-
無(wú)法處理異常時(shí),因使用 throw 而不是 throw ex 拋出一個(gè)新的異常會(huì)造成棧追蹤重置為重新拋出的位置,而不是重用原始拋出位置。因此如果不需要重新拋出不同的異常類型或者不是想故意隱藏原始調(diào)用棧,就應(yīng)使用 throw ,允許相同的異常在調(diào)用棧中向上傳播。
-
避免在 catch 塊中重新拋出異常 如果在開發(fā)中發(fā)現(xiàn)捕獲的異常不能完整或恰當(dāng)?shù)奶幚?,并且需要拋出異常那么我們就需要重新?yōu)化捕獲異常的條件。
-
避免在 when 子句中拋出異常 when 子句拋出異常會(huì)造成表達(dá)式的結(jié)果變?yōu)?false,進(jìn)而不能運(yùn)行 catch 塊。
-
避免以后 when 子句條件改變 這種情況常見于異常會(huì)因本地化而改變,那么這是我們將不得不改變 when 子句的條件。
五、自定義異常處理
一般來(lái)說(shuō)拋出異常時(shí)我們應(yīng)該使用 c# 為我們提供的異常類型。但是某些情況下我們還需自定義異常,例如我們編寫的 API 是由其他語(yǔ)言開發(fā)人員調(diào)用的,這時(shí)我們就不能拋出自己所使用的語(yǔ)言的異常,應(yīng)該自定義異常讓調(diào)用者清晰明了的知道發(fā)什么么錯(cuò)誤。
自定義異常一般都是從 Exception 或者其他異常類派生出來(lái),這是唯一的要求。自定義異常還必須遵循如下三點(diǎn)要求:
-
異常名稱以 Exception 結(jié)尾;
-
必須包含無(wú)參構(gòu)造函數(shù)、包含唯一一個(gè)參數(shù)類型為 string 的構(gòu)造函數(shù)和同時(shí)獲取一個(gè)字符串以及一個(gè)內(nèi)部異常作為參數(shù)的構(gòu)造函數(shù);
-
集成層次不能大于 5 層。
部分程序要求異??梢孕蛄谢@時(shí)我們可以使用可序列化異常。我們只需要在自定義異常類型上加上 System.SerializableAttribute特性 或 實(shí)現(xiàn)ISerializable ,然后添加一個(gè)構(gòu)造函數(shù)來(lái)獲取 SerializationInfo 和 StreamingContext 。這里需要注意的是如果你使用的是 .NET Core 2.0 以下版本那么將無(wú)法使用可序列化異常。
六、總結(jié)
本篇文章講解了一下 C# 中的異常處理,這里我需要提醒各位的是拋出異常會(huì)影響程序的性能,它加載和處理大量額外的運(yùn)行時(shí)棧信息,整個(gè)過(guò)程會(huì)花費(fèi)可觀的時(shí)間,因此我們?cè)诰帉懗绦驎r(shí)應(yīng)盡量避免大量使用拋出異常。
作者簡(jiǎn)介
朱鋼,筆名喵叔,國(guó)內(nèi)某技術(shù)博客認(rèn)證專家,.NET高級(jí)開發(fā)工程師,7年一線開發(fā)經(jīng)驗(yàn),參與過(guò)電子政務(wù)系統(tǒng)和AI客服系統(tǒng)的開發(fā),以及互聯(lián)網(wǎng)招聘網(wǎng)站的架構(gòu)設(shè)計(jì),目前就職于一家初創(chuàng)公司,從事企業(yè)級(jí)安全監(jiān)控系統(tǒng)的開發(fā)。
【原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為.com】
本文標(biāo)題:不看此文,別說(shuō)你懂異常處理
網(wǎng)址分享:http://m.fisionsoft.com.cn/article/dhephsh.html


咨詢
建站咨詢
