關於.NET異常處理的思考

来源:http://www.cnblogs.com/pengze0902/archive/2016/12/21/6185952.html
-Advertisement-
Play Games

年關將至,對於大部分程式員來說,馬上就可以閑下來一段時間了,然而在這個閑暇的時間里,唯有爭論哪門語言更好可以消磨時光,估計最近會有很多關於java與.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類的介紹。在實際的項目中,我們一般不要將異常直接拋出給客戶,我們在編寫程式時,已經考慮程式的容錯性,在程式捕獲到異常後,儘量去恢復程式,或者將異常信息寫入日誌,讓程式進入錯誤頁。如果出現比較嚴重的異常,最後將異常拋出,終止程式。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這篇教程通過實現一個股票報價的小程式來講解如何使用SignalR進行伺服器端的推送,伺服器會模擬股票價格的波動,並把最新的股票價格推送給所有連接的客戶端。該文章參考的是[Server Broadcast with SignalR 2]這篇教程,很不錯的一篇教程,如果有興趣的話可以查看原文,今天記錄下... ...
  • 【HTML寫法標簽】【HTML字體段落標簽】【錨點】【有序無序列表】【表格】 ...
  • 最近學習使用CodeSmith代碼生成器 CodeSmith 是一種語法類似於asp.net的基於模板的代碼生成器,程式可以自定義模板,從而減少重覆編碼的勞動量,提高效率。 作用:CodeSmith 是一種基於模板的代碼生成工具,它使用類似於ASP.NET的語法來生成任意類型的代碼或文本。與其他許多 ...
  • 迴圈、邏輯語句塊 好久不寫博客了,斷更了好幾天了,從上周五到今天,從北京到上海,跨越了1213.0公裡,從一個熟悉的城市到陌生的城市,還好本人適應力比較好,還有感謝小伙伴的接風咯,一切都不是事,好了,進入正題: 本篇還是.NET 基礎部分咯,主要簡述迴圈,判斷: 迴圈: for迴圈 語法: for( ...
  • 一、借鑒說明 1.《Head First Design Patterns》(中文名《深入淺出設計模式》) 2.維基百科,策略模式,https://zh.wikipedia.org/wiki/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F 二、策略模式 基礎知識 將一類相同的... ...
  • "檢索COM類工廠中 CLSID為 {00024500-0000-0000-C000-000000000046}的組件時失敗,原因是出現以下錯誤: 80070005" 問題的解決 ...
  • 菜單收縮有很多種方法具體如何實現還是看個人想法: 第一種通過後臺控制收起與展開: 效果圖: 代碼 : <Grid> <Grid.ColumnDefinitions> <ColumnDefinition x:Name="cd" Width="154"/> <ColumnDefinition /> </ ...
  • 用html實現圖片上傳 後臺採用.net其中在這裡要借用一個js插件 在這裡我會寫一個圖片上傳的一個小Demo,有不全的地方多多包容,和提議, 我把已經寫好的demo已經上傳到百度雲 在這裡可以下載 http://pan.baidu.com/s/1bG2934 開始 html中的內容是 <body> ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...