這篇文章主要介紹了關於.NET異常處理的思考總結,具有一定的參考價值,有興趣的小夥伴們可以參考一下。
年關將至,對於大部分程式設計師來說,馬上就可以閒下來一段時間了,然而在這個閒暇的時間裡,唯有爭論哪門語言更好可以消磨時光,估計最近會有很多關於java與.net的博文出現,我表示要作為一個吃瓜群眾,靜靜的看著大佬們發表心情。
以上的廢話說的夠多了,這裡就不再廢話了,還是切入正題吧。
在專案開發中,對於系統和程式碼的穩定性和容錯性都是有對應的要求。實際開發專案中的程式碼與範例程式碼的差別,更多的是在程式碼的運作的穩定性、容錯性、擴展性的比較。因為對於實作一個功能來說,實作功能的核心程式碼是一樣的,可能只是在寫法上優化而已,但是在實作某一個操作上所使用的類別來說,這一點是絕大多數時候是一樣的。這樣看來,我們在實際開發的過程中,需要考慮的問題比較多,已經不只限於某一具體的功能實現,更多的是程式碼的穩定性和擴展性考慮。
以上是在實際開發中需要面對的問題,筆者在最近的博文中,也在考慮這個異常到底需要怎麼去寫,以及異常到底需要怎麼去理解,希望對大家有一個幫助,也歡迎大家提出自己的想法和意見,分享自己的知識和見解。
一.DotNET異常的概述:
#談到異常,我們就需要知道什麼叫做異常,萬事萬物如果我們想去學習,就應該知道我們要學習的東西是什麼,這樣在心裡也好有一個大概的認知。異常是指成員沒有完成它的名稱宣稱可以完成的行動。在.NET中,建構器、取得和設定屬性、新增和刪除事件、呼叫運算子重載和呼叫轉換運算子等等都沒有辦法回傳錯誤碼,但是在這些建構中又需要回報錯誤,那就必須提供異常處理機制。
在異常的處理中,我們常用的三個區塊分別是:try區塊;catch區塊;finally區塊。這三個區塊可以一起使用,也可以不寫catch區塊使用,異常處理區塊可以巢狀使用,具體的方法在下面會介紹到。
在異常的處理機制中,一般有三種選擇:重新拋出相同的異常,向調用棧高一層的程式碼通知該異常的發生;拋出一個不同的異常,想調用棧高一層程式碼提供更豐富的例外資訊;讓執行緒從catch區塊的底部退出。
有關異常的處理方式,有一些指導性的建議。
1.恰當的使用finally區塊:
finally區塊可以保證不管執行緒拋出什麼類型的例外都可以被執行,finall區塊一般用來做清理那些已經成功啟動的操作,然後再回傳呼叫者或finally區塊之後的程式碼。
2.異常捕捉需適當:
為什麼要適當的捕捉異常呢?如下程式碼,因為我們不能什麼異常都去捕捉,在捕獲異常後,我們需要去處理這些異常,如果我們將所有的異常都捕捉後,但是沒有預見會發生的異常,我們就沒有辦法去處理這些異常。
如果應用程式程式碼拋出一個異常,應用程式的另一端則可能預期要捕捉這個異常,因此不能寫成一個」大小通吃「的異常塊,應該允許該異常在呼叫堆疊中向上移動,讓應用程式程式碼針對性地處理這個異常。
在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); }
3.從異常中恢復:
我們在捕獲異常後,可以針對性的寫一些異常恢復的程式碼,可以讓程式繼續運作。在捕獲異常時,需要捕獲特定的異常,充分的掌握在什麼情況下會拋出異常,並知道從捕獲的異常類型派生出了那些類型。除非在catch區塊的末尾重新拋出異常,否則不要處理或捕獲System.Exception異常。
4.維持狀態:
一般情況下,我們完成一個操作或一個方法時,需要呼叫幾個方法組合完成,在執行的過程中會出現前面幾個方法完成,後面的方法發生異常。發生不可恢復的異常時回滾部分完成的操作,因為我們需要恢復訊息,所有我們在捕獲異常時,需要捕獲所有的異常信息。
5.隱藏實作細節來維持契約:
有時可能需要捕捉一個例外並重新拋出一個不同的例外,這樣可以維繫方法的契約,拋出的心異常類型地應該是一個具體的異常。看如下程式碼:
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。
二.DotNET异常的常用处理机制:
在代码发生异常后,我们需要去处理这个异常,如果一个异常没有得到及时的处理,CLR会终止进程。在异常的处理中,我们可以在一个线程捕获异常,在另一个线程中重新抛出异常。异常抛出时,CLR会在调用栈中向上查找与抛出的异常类型匹配的catch块。如果没有任何catch块匹配抛出的异常类型,就发生一个未处理异常。CLR检测到进程中的任何线程有一个位处理异常,都会终止进程。
1.异常处理块:
(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块之后的语句开始执行。
备注:异常块可以组合和嵌套,对于三个异常块的样例,在这里就不做介绍,异常的嵌套可以防止在处理异常的时候再次出现未处理的异常,以上这些就不再赘述。
2.异常处理实例:
(1).异常处理扩展方法:
/// <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(); }
(2).验证异常:
/// <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."); }
(3).Try-Catch扩展操作:
/// <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的使用,而是给出一些比较通用的方法,主要是一般的开发者对于三个块的使用都有一个认识,就不再做重复的介绍。
三.DotNET的Exception类分析:
CLR允许异常抛出任何类型的实例,这里我们介绍一个System.Exception类:
1.Message属性:指出抛出异常的原因。
[__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()获取运行时资源字符串。
2.StackTrace属性:包含抛出异常之前调用过的所有方法的名称和签名。
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); }
以上是获取堆栈跟踪方法的具体实现,此方法主要用户调试的时候。
3.GetBaseException()获取基础异常信息方法。
[__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; } }
4.ToString()将异常信息格式化。
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类的介绍。在实际的项目中,我们一般不要将异常直接抛出给客户,我们在编写程序时,已经考虑程序的容错性,在程序捕获到异常后,尽量去恢复程序,或者将异常信息写入日志,让程序进入错误页。如果出现比较严重的异常,最后将异常抛出,终止程序。
以上是分析.NET的例外處理的詳細內容。更多資訊請關注PHP中文網其他相關文章!