雜談異常處理try-catch-finally


1.   前言

最近這段時間正開發一個店鋪管理系統,這個項目定位於給中小型店鋪使用的軟件系統。簡單的說,它處理商品的進貨,銷售,退貨等功能。軟件雖小,五臟俱全,里面涉及的技術跟大型應用軟件其實差別也不大,其中有加密、數據訪問、異常處理、日志、驗證、ORM、依賴注入等。

本篇文章主要介紹C#語言的異常處理方面的內容,其中包含的主要內容:

  • 什么是異常?異常的特點?
  • 異常處理的基礎知識。
  • 引發和捕捉異常的處理准則。
  • 避免與異常相關的性能問題的兩種設計模式。
  • 微軟企業庫異常處理模塊。

 

2.   異常概述

  • 在應用程序遇到異常情況(如被零除情況或內存不足警告)時,就會產生異常。
  • 在可能引發異常的語句周圍使用 try 塊。
  • try 塊中發生異常后,控制流會立即跳轉到關聯的異常處理程序(如果存在)。
  • 如果給定異常沒有異常處理程序,則程序將停止執行,並顯示一條錯誤消息。
  • 如果 catch 塊定義了一個異常變量,則可以使用它來獲取有關所發生異常的類型的更多信息。
  • 可能導致異常的操作通過 try 關鍵字來執行。
  • 異常處理程序是在異常發生時執行的代碼塊。在 C# 中,catch 關鍵字用於定義異常處理程序。
  • 程序可以使用 throw 關鍵字顯式地引發異常。
  • 異常對象包含有關錯誤的詳細信息,比如調用堆棧的狀態以及有關錯誤的文本說明。
  • 即使引發了異常,finally 塊中的代碼也會執行,從而使程序可以釋放資源。

 

3.   異常處理基礎知識

3.1.  如何:使用 Try/Catch 塊捕捉異常

將可能引發異常的代碼節放在 Try 塊中,而將處理異常的代碼放在 Catch 塊中。Catch 塊是一系列以關鍵字 catch 開頭的語句,語句后跟異常類型和要執行的操作。

下面的代碼示例使用 Try/Catch 塊捕捉可能的異常。Main 方法包含帶有 StreamReader 語句的 Try 塊,該語句打開名為 data.txt 的數據文件並從該文件寫入字符串。Try 塊后面是 Catch 塊,該塊捕捉 Try 塊產生的任何異常。

 

using System;

using System.IO;

using System.Security.Permissions;

// Security permission request.

[assembly:FileIOPermissionAttribute(SecurityAction.RequestMinimum, All = @"c:\data.txt")]

public class ProcessFile {

    public static void Main() {

        try {

            StreamReader sr = File.OpenText("data.txt");

            Console.WriteLine("The first line of this file is {0}", sr.ReadLine());   

        }

        catch(Exception e) {

            Console.WriteLine("An error occurred: '{0}'", e);

        }

    }

}

 

3.2.  如何:在 Catch 塊中使用特定異常

發生異常時,異常沿堆棧向上傳遞,每個 Catch 塊都有機會處理它。Catch 語句的順序很重要。將針對特定異常的 Catch 塊放在常規異常 Catch 塊的前面,否則編譯器可能會發出錯誤。確定正確 Catch 塊的方法是將異常的類型與 Catch 塊中指定的異常名稱進行匹配。如果沒有特定的 Catch 塊,則由可能存在的常規 Catch 塊捕捉異常。

 

下面的代碼示例使用 try/catch 塊捕獲 InvalidCastException。該示例創建一個名為 Employee 的類,它帶有一個屬性:職員級別 (Emlevel)。PromoteEmployee 方法取得對象並增加職員級別。將 DateTime 實例傳遞給 PromoteEmployee 方法時,發生 InvalidCastException。

 

using System;

public class Employee

{

   //Create employee level property.

   public int Emlevel

   {

      get

         {

         return(emlevel);

         }

      set

         {

         emlevel = value;

         }

   }

   int emlevel;

}

public class Ex13

{

   public static void PromoteEmployee(Object emp)

   {

   //Cast object to Employee.

   Employee e = (Employee) emp;

   // Increment employee level.

   e.Emlevel = e.Emlevel + 1;

   }

 

   public static void Main()

   {

   try

      {

   Object o = new Employee();

   DateTime newyears = new DateTime(2001, 1, 1);

   //Promote the new employee.

   PromoteEmployee(o);

   //Promote DateTime; results in InvalidCastException as newyears is not an employee instance.

   PromoteEmployee(newyears);

      }

   catch (InvalidCastException e)

      {

      Console.WriteLine("Error passing data to PromoteEmployee method. " + e);

      }

   }

}

 

3.3.  如何:顯式引發異常

可以使用 throw 語句顯式引發異常。還可以使用 throw 語句再次引發捕獲的異常。較好的編碼做法是,向再次引發的異常添加信息以在調試時提供更多信息。

 

下面的代碼示例使用 try/catch 塊捕獲可能的 FileNotFoundException。try 塊后面是 catch 塊,catch 塊捕獲 FileNotFoundException,如果找不到數據文件,則向控制台寫入消息。下一條語句是 throw 語句,該語句引發新的 FileNotFoundException 並向該異常添加文本信息。

 

using System;

using System.IO;

 

public class ProcessFile

{

   public static void Main()

      {

      FileStream fs = null;

      try  

      {

         //Opens a text tile.

         fs = new FileStream(@"C:\temp\data.txt", FileMode.Open);

         StreamReader sr = new StreamReader(fs);

         string line;

 

         //A value is read from the file and output to the console.

         line = sr.ReadLine();

         Console.WriteLine(line);

      }

      catch(FileNotFoundException e)

      {

         Console.WriteLine("[Data File Missing] {0}", e);

         throw new FileNotFoundException(@"data.txt not in c:\temp directory]",e);

      }

      finally

      {

         if (fs != null)

            fs.Close();

      }

   }

}

 

3.4.  如何:使用 Finally 塊

異常發生時,執行將終止,並且控制交給最近的異常處理程序。這通常意味着不執行希望總是調用的代碼行。有些資源清理(如關閉文件)必須總是執行,即使有異常發生。為實現這一點,可以使用 Finally 塊。Finally 塊總是執行,不論是否有異常發生。

 

下面的代碼示例使用 try/catch 塊捕獲 ArgumentOutOfRangeException。Main 方法創建兩個數組並試圖將一個數組復制到另一個數組。該操作生成 ArgumentOutOfRangeException,同時錯誤被寫入控制台。Finally 塊執行,不論復制操作的結果如何。

using System;

class ArgumentOutOfRangeExample

{

         static public void Main()

  {

  int[] array1={0,0};

  int[] array2={0,0};

     try

     {

     Array.Copy(array1,array2,-1);

     }

     catch (ArgumentOutOfRangeException e)

     {

     Console.WriteLine("Error: {0}",e);

     }

     finally

     {

     Console.WriteLine("This statement is always executed.");

     }

  }

}

 

4.   異常設計准則

4.1.  異常引發

  • 不要返回錯誤代碼。異常是報告框架中的錯誤的主要手段。
  • 盡可能不對正常控制流使用異常。除了系統故障及可能導致爭用狀態的操作之外,框架設計人員還應設計一些 API 以便用戶可以編寫不引發異常的代碼。例如,可以提供一種在調用成員之前檢查前提條件的方法,以便用戶可以編寫不引發異常的代碼。
  • 不要包含可以根據某一選項引發或不引發異常的公共成員。
  • 不要包含將異常作為返回值或輸出參數返回的公共成員。
  • 考慮使用異常生成器方法。從不同的位置引發同一異常會經常發生。為了避免代碼膨脹,請使用幫助器方法創建異常並初始化其屬性。
  • 避免從 finally 塊中顯式引發異常。可以接受因調用引發異常的方法而隱式引發的異常。

 

4.2.  異常處理

  • 不要通過在框架代碼中捕捉非特定異常(如 System.Exception、System.SystemException 等)來處理錯誤。
  • 避免通過在應用程序代碼中捕捉非特定異常(如 System.Exception、System.SystemException 等)來處理錯誤。某些情況下,可以在應用程序中處理錯誤,但這種情況極。
  • 如果捕捉異常是為了傳輸異常,則不要排除任何特殊異常。
  • 如果了解特定異常在給定上下文中引發的條件,請考慮捕捉這些異常。
  • 不要過多使用 catch。通常應允許異常在調用堆棧中往上傳播。
  • 使用 try-finally 並避免將 try-catch 用於清理代碼。在書寫規范的異常代碼中,try-finally 遠比 try-catch 更為常用。
  • 捕捉並再次引發異常時,首選使用空引發。這是保留異常調用堆棧的最佳方式。
  • 不要使用無參數 catch 塊來處理不符合 CLS 的異常(不是從 System.Exception 派生的異常)。支持不是從 Exception 派生的異常的語言可以處理這些不符合 CLS 的異常。

 

5.   兩種設計模式

5.1.  Tester-Doer 模式

Doer 部分

public class Doer

{

    public static void ProcessMessage(string message)

    {

        if (message == null)

        {

            throw new ArgumentNullException("message");

        }

    }

}

 

Tester部分

public class Tester

{

    public static void TesterDoer(ICollection<string> messages)

    {

        foreach (string message in messages)

        {

            if (message != null)

            {

                Doer.ProcessMessage(message);

            }

        }

    }

}

 

5.2.  TryParse 模式

TryParse 方法類似於 Parse 方法,不同之處在於 TryParse 方法在轉換失敗時不引發異常。

Parse方法

public void Do()

{

    string s = “a”;

    double d;

    try

    {

        d = Double.Parse(s);

    }

    catch (Exception ex)

    {

        d = 0;

    }

}

 

TryParse方法

public void TryDo()

{

    string s = "a";

    double d;

    if (double.TryParse(s, out d) == false)

    {

        d = 0;

    }

}

 

6.   微軟企業庫異常處理模塊

6.1.  創建自定義異常包裝類

public class BusinessLayerException : ApplicationException

{

     public BusinessLayerException() : base()

     {

     }

 

     public BusinessLayerException(string message) : base(message)

     {

     }

    

     public BusinessLayerException(string message, Exception exception) :

         base(message, exception)

     {

     }

 

     protected BusinessLayerException(SerializationInfo info, StreamingContext context) :

         base(info, context)

     {

     }

}

 

6.2.  配置異常處理

<add name="Wrap Policy">

        <exceptionTypes>

          <add type="System.Data.DBConcurrencyException, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

            postHandlingAction="ThrowNewException" name="DBConcurrencyException">

            <exceptionHandlers>

              <add exceptionMessage="Wrapped Exception: A recoverable error occurred while attempting to access the database."

                exceptionMessageResourceType="" wrapExceptionType="ExceptionHandlingQuickStart.BusinessLayer.BusinessLayerException, ExceptionHandlingQuickStart.BusinessLayer"

                type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WrapHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

                name="Wrap Handler" />

            </exceptionHandlers>

          </add>

        </exceptionTypes>

      </add>

 

6.3.  編寫代碼

public bool ProcessWithWrap()

{

    try

    {

         this.ProcessB();

    }

    catch(Exception ex)

    {

         // Quick Start is configured so that the Wrap Policy will

         // log the exception and then recommend a rethrow.

         bool rethrow = ExceptionPolicy.HandleException(ex, "Wrap Policy");

 

         if (rethrow)

         {

             throw;            

         }             

    }

 

    return true;

}

 

6.4.  參考

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM