關於.NET異常處理的思考(上)

黄舟
發布: 2017-02-06 14:43:14
原創
1192 人瀏覽過

在專案開發中,對於系統和程式碼的穩定性和容錯性都是有對應的要求。實際開發專案中的程式碼與範例程式碼的差別,更多的是在程式碼的運作的穩定性、容錯性、擴展性的比較。

因為對於實現一個功能來說,實現功能的核心代碼是一樣的,可能只是在寫法上優化而已,但是在實現某一個操作上使用的類來說,這一點是絕大多數時候是一樣的。

這樣看來,我們在實際開發的過程中,需要考慮的問題比較多,已經不僅僅局限於某一具體的功能實現,更多的是代碼的穩定性和擴展性考慮。

以上是在實際開發中需要面對的問題,筆者在最近的博文中,也在考慮這個異常到底需要怎麼去寫,以及異常到底需要怎麼去理解,在博文中,也有不少的園友對異常的寫法和處理提出了自己的意見,在這裡我就寫一下自己的一些理解,可能寫的比較淺顯和粗略,但是只當是一個引子,可以引出大佬們來談談自己的實際項目經驗。希望對大家有一個幫助,也歡迎大家提出自己的想法和意見,分享自己的知識和見解。

一.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&#39;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&#39;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&#39;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&#39;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的使用,而是给出一些比较通用的方法,主要是一般的开发者对于三个块的使用都有一个认识,就不再做重复的介绍。

以上就是关于.NET异常处理的思考(上)的内容,更多相关内容请关注PHP中文网(www.php.cn)!


相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!