跳转到内容

C Sharp/Exceptions

维基教科书,自由的教学读本

介绍

[编辑]

所有异常对象是System.Exception类或其子类的实例。.NET Framework定义了很多异常类。程序员可自定义异常类。

2.0版之前要求自定义异常类应该是ApplicationException异常类的子类。从2.0版开始要求自定义异常类派生自Exception[1]

概况

[编辑]

有3种异常处理代码用法:

  • try/catch - 做事并捕获可能出现的异常。
  • try/catch/finally - 做事并捕获可能出现的异常,且总是finally
  • try/finally - 做事,且总是finally。如果发生异常,会在finally执行抛出。

异常捕获是按照从最特殊到最不特殊的顺序。例如,试图访问一个不存在的代码,CLR查询异常处理器的顺序:

  • FileNotFoundException
  • IOException (FileNotFoundException的基类)
  • SystemException (IOException的基类)
  • Exception (SystemException的基类)

CLR扫描并查找相匹配的finally子句的过程:开始于引发异常的方法,结束于最顶层的捕获了异常的方法;在所有“下层”finally子句执行结束之后,相应的catch子句所指定的异常处理代码块才开始执行;之后,与此catch子句“同层”的finally子句所指定的异常处理代码块得到执行。

例子

[编辑]

try/catch

[编辑]
class ExceptionTest
{
     public static void Main(string[] args)
     {
          try
          {
               Console.WriteLine(args[0]);
               Console.WriteLine(args[1]);
               Console.WriteLine(args[2]);
               Console.WriteLine(args[3]);
               Console.WriteLine(args[4]);
          }
          catch (ArgumentOutOfRangeException e)
          {
               Console.WriteLine(e.Message);
          }
     }
}

多个异常捕获的例子:

class ExceptionTest
{
     public static void Main(string[] args)
     {
          try
          {
               string fileContents = new StreamReader(@"C:\log.txt").ReadToEnd();
          }
          catch (UnauthorizedAccessException e) // Access problems
          {
               Console.WriteLine(e.Message);
          }
          catch (FileNotFoundException e)       // File does not exist
          {
               Console.WriteLine(e.Message);
          }
          catch (IOException e)                // Some other IO problem.
          {
               Console.WriteLine(e.Message);
          }
     }
}

在所有catch语句中可以忽略异常对象的类型和变量名:

try
{
    int number = 1/0;
}
catch (DivideByZeroException)
{
    // DivideByZeroException
}
catch
{
    // some other exception
}

try/catch/finally

[编辑]
using System;
class ExceptionTest
{
     public static void Main(string[] args)
     {
          SqlConnection sqlConn = null;

          try
          {
              sqlConn = new SqlConnection ( /*Connection here*/ );
              sqlConn.Open();
 
              // Various DB things
        
              // Notice you do not need to explicitly close the connection, as .Dispose() does this for you.
          }
          catch (SqlException e)
          {
               Console.WriteLine(e.Message);
          }
          finally
          {
               if (sqlConn != null && sqlConn.State != ConnectionState.Closed)
               {
                   sqlConn.Dispose();
               }
          }
     }
}

注意SqlConnection对象在try/catch/finally之外声明。因为try/catch内声明的变量在finally内都是不可见的。

try/finally

[编辑]

异常抛出后未能捕获,则将继续抛到调用栈。

class ExceptionTest
{
     public static void Main(string[] args)
     {
          SqlConnection sqlConn = null;

          try
          {
              SqlConnection sqlConn = new SqlConnection ( /*Connection here*/ );
              sqlConn.Open();
 
              // Various DB bits
          }
          finally
          {
               if (sqlConn != null && sqlConn.State != ConnectionState.Closed)
               {
                   sqlConn.Dispose();
               }
          }
     }
}

重新抛出异常

[编辑]

如何抛出异常

[编辑]

下述风格代码是不推荐的:

try
{
      // Do something
}
catch (Exception ex)
{
      // Ignore this here
}

因为对于OutOfMemoryExceptionNullReferenceException,程序已经无法继续了。

另一种坏的实践:

try
{
   ..
}
catch (Exception ex)
{
     throw ex;
}

CLR将认为throw ex;语句是问题根源,虽然实际上问题出自try块内。

如何捕获异常

[编辑]

更好的方式是:

/* Read the config file, and return the integer value. If it does not exist, then this is a problem! */

try
{
     string value = ConfigurationManager.AppSettings["Timeout"];

     if (value == null)
         throw new ConfigurationErrorsException("Timeout value is not in the configuration file.");
}
catch (Exception ex )
{
     throw; // <-- Throw the existing problem!
}

关键字throw;意味着保持异常信息并抛到上一层调用栈。

异常带上额外的信息

[编辑]
public OrderItem LoadItem(string itemNumber)
{
    DataTable dt = null;

    try
    {
         if (itemNumber == null)
              throw new ArgumentNullException("Item Number cannot be null","itemNumber");

         DataTable dt = DataAccess.OrderItem.Load(itemNumber);
  
         if (dt.Rows == 0)
              return null;
         else if (dt.Rows > 1)
              throw new DuplicateDataException( "Multiple items map to this item.",itemNumber, dt);

         OrderItem item = OrderItem.CreateInstanceFromDataRow(dt.Rows[0]);

         if (item == null)
              throw new ErrorLoadingException("Error loading Item " + itemNumber, itemNumber, dt.Rows[0]);
    }
    catch (DuplicateDataException dde)
    {
         throw new ErrorLoadingException("OrderItem.LoadItem failed with Item " + 
                                                            itemNumber, dde); // <-- Include dde (as the InnerException) parameter
    }
    catch (Exception ex)
    {
         throw; // <-- We aren't expecting any other problems, so throw them if they occur.
    }
}

参考文献

[编辑]
  1. [ApplicationException made obsolete]