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]