杂谈异常处理try-catch-finally

时间:2022-12-15 16:05:42

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.  参考