年關將至,對於大部分程序員來講,立刻就能夠閒下來一段時間了,然而在這個閒暇的時間裏,惟有爭論哪門語言更好能夠消磨時光,估計最近會有不少關於java與.net的博文出現,我表示要做爲一個吃瓜羣衆,靜靜的看着大佬們發表心情。java
以上的廢話說的夠多了,這裏就再也不廢話了,仍是切入正題吧。程序員
在項目開發中,對於系統和代碼的穩定性和容錯性都是有對應的要求。實際開發項目中的代碼與樣例代碼的區別,更多的是在代碼的運行的穩定性、容錯性、擴展性的比較。由於對於實現一個功能來講,實現功能的核心代碼是同樣的,可能只是在寫法上優化而已,可是在實現某一個操做上使用的類來講,這一點是絕大多數時候是同樣的。這樣看來,咱們在實際開發的過程當中,須要考慮的問題比較多,已經不只僅侷限於某一具體的功能實現,更多的是代碼的穩定性和擴展性考慮。c#
以上是在實際開發中須要面對的問題,筆者在最近的博文中,也在考慮這個異常到底須要怎麼去寫,以及異常到底須要怎麼去理解,在博文中,也有很多的園友對異常的寫法和處理提出了本身的意見,在這裏我就寫一下本身的一些理解,可能寫的比較淺顯和粗略,可是隻當是一個引子,能夠引出大佬們來談談本身的實際項目經驗。但願對你們有一個幫助,也歡迎你們提出本身的想法和意見,分享本身的知識和看法。安全
談到異常,咱們就須要知道什麼叫作異常,萬事萬物若是咱們想去學習,就應該知道咱們要學習的東西是什麼,這樣在內心也好有一個大概的認知。異常是指成員沒有完成它的名稱宣稱能夠完成的行動。在.NET中,構造器、獲取和設置屬性、添加和刪除事件、調用操做符重載和調用轉換操做符等等都沒有辦法返回錯誤代碼,可是在這些構造中又須要報告錯誤,那就必須提供異常處理機制。app
在異常的處理中,咱們常用到的三個塊分別是:try塊;catch塊;finally塊。這三個塊能夠一塊兒使用,也能夠不寫catch塊使用,異常處理塊能夠嵌套使用,具體的方法在下面會介紹到。ide
在異常的處理機制中,通常有三種選擇:從新拋出相同的異常,向調用棧高一層的代碼通知該異常的發生;拋出一個不一樣的異常,想調用棧高一層代碼提供更豐富的異常信息;讓線程從catch塊的底部退出。 學習
有關異常的處理方式,有一些指導性的建議。測試
finally塊能夠保證無論線程拋出什麼類型的異常均可以被執行,finall塊通常用來作清理那些已經成功啓動的操做,而後再返回調用者或者finally塊以後的代碼。優化
爲何要適當的捕捉異常呢?以下代碼,由於咱們不能什麼異常都去捕捉,在捕獲異常後,咱們須要去處理這些異常,若是咱們將全部的異常都捕捉後,可是沒有預見會發生的異常,咱們就沒有辦法去處理這些異常。ui
若是應用程序代碼拋出一個異常,應用程序的另外一端則可能預期要捕捉這個異常,所以不能寫成一個」大小通吃「的異常塊,應該容許該異常在調用棧中向上移動,讓應用程序代碼針對性地處理這個異常。
在catch塊中,可使用System.Exception捕捉異常,可是最好在catch塊末尾從新拋出異常。至於緣由在後面會講解到。
try { var hkml = GetRegistryKey(rootKey); var subkey = hkml.CreateSubKey(subKey); if (subkey != null && keyName != string.Empty) subkey.SetValue(keyName, keyValue, RegistryValueKind.String); } catch (Exception ex) { Log4Helper.Error("建立註冊表錯誤" + ex); throw new Exception(ex.Message,ex); }
咱們在捕獲異常後,能夠針對性的寫一些異常恢復的代碼,可讓程序繼續運行。在捕獲異常時,須要捕獲具體的異常,充分的掌握在什麼狀況下會拋出異常,並知道從捕獲的異常類型派生出了那些類型。除非在catch塊的末尾從新拋出異常,不然不要處理或捕獲System.Exception異常。
通常狀況下,咱們完成一個操做或者一個方法時,須要調用幾個方法組合完成,在執行的過程當中會出現前面幾個方法完成,後面的方法發生異常。發生不可恢復的異常時回滾部分完成的操做,由於咱們須要恢復信息,全部咱們在捕獲異常時,須要捕獲全部的異常信息。
有時可能須要捕捉一個異常並從新拋出一個不一樣的異常,這樣能夠維繫方法的契約,拋出的心異常類型地應該是一個具體的異常。看以下代碼:
FileStream fs = null; try { fs = FileStream(); } catch (FileNotFoundException e) {
//拋出一個不一樣的異常,將異常信息包含在其中,並將原來的異常設置爲內部異常 throw new NameNotFoundException(); } catch (IOException e) {
//拋出一個不一樣的異常,將異常信息包含在其中,並將原來的異常設置爲內部異常
throw new NameNotFoundException();
}
finally
{
if (fs != null)
{
fs.close();
}
}
以上的代碼只是在說明一種處理方式。應該讓拋出的全部異常都沿着方法的調用棧向上傳遞,而不是把他們」吞噬「了以後拋出一個新的異常。若是一個類型構造器拋出一個異常,並且該異常未在類型構造器方法中捕獲,CLR就會在內部捕獲該異常,並改成拋出一個新的TypeInitialztionException。
在代碼發生異常後,咱們須要去處理這個異常,若是一個異常沒有獲得及時的處理,CLR會終止進程。在異常的處理中,咱們能夠在一個線程捕獲異常,在另外一個線程中從新拋出異常。異常拋出時,CLR會在調用棧中向上查找與拋出的異常類型匹配的catch塊。若是沒有任何catch塊匹配拋出的異常類型,就發生一個未處理異常。CLR檢測到進程中的任何線程有一個位處理異常,都會終止進程。
(1).try塊:包含代碼一般須要執行一些通用的資源清理操做,或者須要從異常中恢復,或者二者都須要。try塊還能夠包含也許會拋出異常的代碼。一個try塊至少有一個關聯的catch塊或finall塊。
(2).catch塊:包含的是響應一個異常須要執行的代碼。catch關鍵字後的圓括號中的表達式是捕獲類型。捕獲類型從System.Exception或者其派生類指定。CLR自上而下搜素一個匹配的catch塊,因此應該教具體的異常放在頂部。一旦CLR找到一個具備匹配捕獲類型的catch塊,就會執行內層全部finally塊中的代碼,」內層finally「是指拋出異常的tey塊開始,到匹配異常的catch塊之間的全部finally塊。
使用System.Exception捕捉異常後,能夠採用在catch塊的末尾從新拋出異常,由於若是咱們在捕獲Exception異常後,沒有及時的處理或者終止程序,這一異常可能對程序形成很大的安全隱患,Exception類是全部異常的基類,能夠捕獲程序中全部的異常,若是出現較大的異常,咱們沒有及時的處理,形成的問題是巨大的。
(3).finally塊:包含的代碼是保證會執行的代碼。finally塊的全部代碼執行完畢後,線程退出finally塊,執行緊跟在finally塊以後的語句。若是不存在finally塊,線程將從最後一個catch塊以後的語句開始執行。
備註:異常塊能夠組合和嵌套,對於三個異常塊的樣例,在這裏就不作介紹,異常的嵌套能夠防止在處理異常的時候再次出現未處理的異常,以上這些就再也不贅述。
/// <summary> /// 格式化異常消息 /// </summary> /// <param name="e">異常對象</param> /// <param name="isHideStackTrace">是否隱藏異常規模信息</param> /// <returns>格式化後的異常信息字符串</returns> public static string FormatMessage(this Exception e, bool isHideStackTrace = false) { var sb = new StringBuilder(); var count = 0; var appString = string.Empty; while (e != null) { if (count > 0) { appString += " "; } sb.AppendLine(string.Format("{0}異常消息:{1}", appString, e.Message)); sb.AppendLine(string.Format("{0}異常類型:{1}", appString, e.GetType().FullName)); sb.AppendLine(string.Format("{0}異常方法:{1}", appString, (e.TargetSite == null ? null : e.TargetSite.Name))); sb.AppendLine(string.Format("{0}異常源:{1}", appString, e.Source)); if (!isHideStackTrace && e.StackTrace != null) { sb.AppendLine(string.Format("{0}異常堆棧:{1}", appString, e.StackTrace)); } if (e.InnerException != null) { sb.AppendLine(string.Format("{0}內部異常:", appString)); count++; } e = e.InnerException; } return sb.ToString(); }
/// <summary> /// 檢查字符串是空的或空的,並拋出一個異常 /// </summary> /// <param name="val">值測試</param> /// <param name="paramName">參數檢查名稱</param> public static void CheckNullOrEmpty(string val, string paramName) { if (string.IsNullOrEmpty(val)) throw new ArgumentNullException(paramName, "Value can't be null or empty"); } /// <summary> /// 請檢查參數不是空的或空的,並拋出異常 /// </summary> /// <param name="param">檢查值</param> /// <param name="paramName">參數名稱</param> public static void CheckNullParam(string param, string paramName) { if (string.IsNullOrEmpty(param)) throw new ArgumentNullException(paramName, paramName + " can't be neither null nor empty"); } /// <summary> /// 檢查參數不是無效,並拋出一個異常 /// </summary> /// <param name="param">檢查值</param> /// <param name="paramName">參數名稱</param> public static void CheckNullParam(object param, string paramName) { if (param == null) throw new ArgumentNullException(paramName, paramName + " can't be null"); } /// <summary> /// 請檢查參數1不一樣於參數2 /// </summary> /// <param name="param1">值1測試</param> /// <param name="param1Name">name of value 1</param> /// <param name="param2">value 2 to test</param> /// <param name="param2Name">name of vlaue 2</param> public static void CheckDifferentsParams(object param1, string param1Name, object param2, string param2Name) { if (param1 == param2) { throw new ArgumentException(param1Name + " can't be the same as " + param2Name, param1Name + " and " + param2Name); } } /// <summary> /// 檢查一個整數值是正的(0或更大) /// </summary> /// <param name="val">整數測試</param> public static void PositiveValue(int val) { if (val < 0) throw new ArgumentException("The value must be greater than or equal to 0."); }
/// <summary> /// 對某對象執行指定功能與後續功能,並處理異常狀況 /// </summary> /// <typeparam name="T">對象類型</typeparam> /// <param name="source">值</param> /// <param name="action">要對值執行的主功能代碼</param> /// <param name="failureAction">catch中的功能代碼</param> /// <param name="successAction">主功能代碼成功後執行的功能代碼</param> /// <returns>主功能代碼是否順利執行</returns> public static bool TryCatch<T>(this T source, Action<T> action, Action<Exception> failureAction, Action<T> successAction) where T : class { bool result; try { action(source); successAction(source); result = true; } catch (Exception obj) { failureAction(obj); result = false; } return result; } /// <summary> /// 對某對象執行指定功能,並處理異常狀況 /// </summary> /// <typeparam name="T">對象類型</typeparam> /// <param name="source">值</param> /// <param name="action">要對值執行的主功能代碼</param> /// <param name="failureAction">catch中的功能代碼</param> /// <returns>主功能代碼是否順利執行</returns> public static bool TryCatch<T>(this T source, Action<T> action, Action<Exception> failureAction) where T : class { return source.TryCatch(action, failureAction, obj => { }); } /// <summary> /// 對某對象執行指定功能,並處理異常狀況與返回值 /// </summary> /// <typeparam name="T">對象類型</typeparam> /// <typeparam name="TResult">返回值類型</typeparam> /// <param name="source">值</param> /// <param name="func">要對值執行的主功能代碼</param> /// <param name="failureAction">catch中的功能代碼</param> /// <param name="successAction">主功能代碼成功後執行的功能代碼</param> /// <returns>功能代碼的返回值,若是出現異常,則返回對象類型的默認值</returns> public static TResult TryCatch<T, TResult>(this T source, Func<T, TResult> func, Action<Exception> failureAction, Action<T> successAction) where T : class { TResult result; try { var u = func(source); successAction(source); result = u; } catch (Exception obj) { failureAction(obj); result = default(TResult); } return result; } /// <summary> /// 對某對象執行指定功能,並處理異常狀況與返回值 /// </summary> /// <typeparam name="T">對象類型</typeparam> /// <typeparam name="TResult">返回值類型</typeparam> /// <param name="source">值</param> /// <param name="func">要對值執行的主功能代碼</param> /// <param name="failureAction">catch中的功能代碼</param> /// <returns>功能代碼的返回值,若是出現異常,則返回對象類型的默認值</returns> public static TResult TryCatch<T, TResult>(this T source, Func<T, TResult> func, Action<Exception> failureAction) where T : class { return source.TryCatch(func, failureAction, obj => { }); }
本文沒有具體介紹try,catch,finally的使用,而是給出一些比較通用的方法,主要是通常的開發者對於三個塊的使用都有一個認識,就再也不作重複的介紹。
CLR容許異常拋出任何類型的實例,這裏咱們介紹一個System.Exception類:
[__DynamicallyInvokable] public virtual string Message { [__DynamicallyInvokable] get { if (this._message != null) { return this._message; } if (this._className == null) { this._className = this.GetClassName(); } return Environment.GetRuntimeResourceString("Exception_WasThrown", new object[] { this._className }); } }
由以上的代碼能夠看出,Message只具備get屬性,因此message是隻讀屬性。GetClassName()獲取異常的類。GetRuntimeResourceString()獲取運行時資源字符串。
public static string StackTrace { [SecuritySafeCritical] get { new EnvironmentPermission(PermissionState.Unrestricted).Demand(); return GetStackTrace(null, true); } }
EnvironmentPermission()用於環境限制,PermissionState.Unrestricted設置權限狀態,GetStackTrace()獲取堆棧跟蹤,具體看一下GetStackTrace()的代碼。
internal static string GetStackTrace(Exception e, bool needFileInfo) { StackTrace trace; if (e == null) { trace = new StackTrace(needFileInfo); } else { trace = new StackTrace(e, needFileInfo); } return trace.ToString(StackTrace.TraceFormat.Normal); }
public StackTrace(Exception e, bool fNeedFileInfo) { if (e == null) { throw new ArgumentNullException("e"); } this.m_iNumOfFrames = 0; this.m_iMethodsToSkip = 0; this.CaptureStackTrace(0, fNeedFileInfo, null, e); }
以上是獲取堆棧跟蹤方法的具體實現,此方法主要用戶調試的時候。
[__DynamicallyInvokable] public virtual Exception GetBaseException() { Exception innerException = this.InnerException; Exception exception2 = this; while (innerException != null) { exception2 = innerException; innerException = innerException.InnerException; } return exception2; }
InnerException屬性是內在異常,這是一個虛方法,在這裏被重寫。具體看一下InnerException屬性。
[__DynamicallyInvokable] public Exception InnerException { [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get { return this._innerException; } }
private string ToString(bool needFileLineInfo, bool needMessage) { string className; string str = needMessage ? this.Message : null; if ((str == null) || (str.Length <= 0)) { className = this.GetClassName(); } else { className = this.GetClassName() + ": " + str; } if (this._innerException != null) { className = className + " ---> " + this._innerException.ToString(needFileLineInfo, needMessage) + Environment.NewLine + " " + Environment.GetRuntimeResourceString("Exception_EndOfInnerExceptionStack"); } string stackTrace = this.GetStackTrace(needFileLineInfo); if (stackTrace != null) { className = className + Environment.NewLine + stackTrace; } return className; }
在此方法中,將獲取的異常信息進行格式化爲字符串,this.GetClassName() 獲取異常類的相關信息。
以上咱們注意到[__DynamicallyInvokable]定製屬性,咱們看一下具體的實現代碼:
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] public __DynamicallyInvokableAttribute() { }
以上咱們主要註釋部分,」圖像邊界「這個屬性的相關信息,請參見《Via CLR c#》,這裏就不作具體的介紹。
以上在對異常的介紹中,主要介紹了CLR的異常處理機制,一些較爲通用的異常代碼,以及對Exception類的介紹。在實際的項目中,咱們通常不要將異常直接拋出給客戶,咱們在編寫程序時,已經考慮程序的容錯性,在程序捕獲到異常後,儘可能去恢復程序,或者將異常信息寫入日誌,讓程序進入錯誤頁。若是出現比較嚴重的異常,最後將異常拋出,終止程序。