程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> .NET中異常處理的幾個誤區

.NET中異常處理的幾個誤區

編輯:關於.NET

有些人認為下面代碼就是一個catch的錯誤用法:

catch(Exception e)
{
   throw e;
}

首先說明,這不是一個錯誤用法,但是通常來講,我們應該避免這種代碼。然後要說明的是,這段代碼有一個比較典型的作用就是改變異常出現的位置,也就是可以對某類異常統一在一個位置處理。先看下面代碼:

public int GetAllCount2()
   {
     try
     {
       openDB();
       int i = 1;
       return i;
     }
     catch (SqlException sex)
     {
       throw sex;
     }
     catch (Exception ex)
     {
       throw ex;
     }
   }
   public int GetAllCount()
   {
     openDB(); // 這裡也可能是微軟企業類庫等
     int i = 1;
     return i;
   }
   private void openDB()
   {
     conn.Open();
   }

假設我們有一個公用方法叫openDB(),而很多方法中調用它,當數據庫打開失敗的時候,對於調用GetAllCount方法,異常將定位於conn.Open而如果調用GetAllCount2,那麼異常定位於throw sex的位置,同時堆棧信息也有所不同,可以更快捷的找到調用方法的位置,也可在此位置進行一些錯誤恢復處理。尤其是我們編寫一些底層類庫的時候,比如Framework類庫從不會把異常代碼定位到Framework類庫內部的某個方法上面。但是需要注意的是我們盡量避免捕獲異常而不返回,例如:

catch(){}

這樣的使用就是典型的錯誤使用了,因為對於Framework來講,任何時候系統都可能拋出一個StackOverflowException或者OutOfMemoryExcetpion而上面這段代碼則隱藏了這些異常,有時候則導致一些嚴重的問題。

對於異常處理,在性能上有2點注意

第一點:在使用try/catch時,如果不發生異常,那麼幾乎可以忽略性能的損失。

關於這一點,這裡我們進行一些深入分析,對此比較了解的可以跳過本節。首先,讓我們先看一下try/catch的IL表現。我們有2個方法,一個使用try/catch,而另一個未做任何處理:

static int Test1(int a, int b)
{
   try
   {
     if (a > b)
       return a;
     return b;
   }
   catch
   {
     return -1;
   }
}
static int Test2(int a, int b)
{
   if (a > b)
     return a;
   return b;
}

使用ILDasm工具查看,IL代碼分別如下:(這裡之所以引入IL,是因為IL是比較接近機器匯編,所以在IL中我們可以更清楚的了解代碼的執行情況,對IL沒有興趣的可以跳過此節)

.method private hidebysig static int32 Test1(int32 a,
                        int32 b) cil managed
{
  // 代碼大小    30 (0x1e)
  .maxstack 2
  .locals init ([0] int32 CS$1$0000,
      [1] bool CS$4$0001)
  IL_0000: nop
  .try
  {
   IL_0001: nop
   IL_0002: ldarg.0
   IL_0003: ldarg.1
   IL_0004: cgt
   IL_0006: ldc.i4.0
   IL_0007: ceq
   IL_0009: stloc.1
   IL_000a: ldloc.1
   IL_000b: brtrue.s  IL_0011
   IL_000d: ldarg.0
   IL_000e: stloc.0
   IL_000f: leave.s  IL_001b
   IL_0011: ldarg.1
   IL_0012: stloc.0
   IL_0013: leave.s  IL_001b
  } // end .try
  catch [mscorlib]System.Object
  {
   IL_0015: pop
   IL_0016: nop
   IL_0017: ldc.i4.m1
   IL_0018: stloc.0
   IL_0019: leave.s  IL_001b
  } // end handler
  IL_001b: nop
  IL_001c: ldloc.0
  IL_001d: ret
} // end of method Program::Test1

Test2

.method private hidebysig static int32 Test2(int32 a,
                        int32 b) cil managed
{
  // 代碼大小    22 (0x16)
  .maxstack 2
  .locals init ([0] int32 CS$1$0000,
      [1] bool CS$4$0001)
  IL_0000: nop
  IL_0001: ldarg.0
  IL_0002: ldarg.1
  IL_0003: cgt
  IL_0005: ldc.i4.0
  IL_0006: ceq
  IL_0008: stloc.1
  IL_0009: ldloc.1
  IL_000a: brtrue.s  IL_0010
  IL_000c: ldarg.0
  IL_000d: stloc.0
  IL_000e: br.s    IL_0014
  IL_0010: ldarg.1
  IL_0011: stloc.0
  IL_0012: br.s    IL_0014
  IL_0014: ldloc.0
  IL_0015: ret
} // end of method Program::Test2

這裡我們只需關注紅字高亮的幾行即可。此處我們只關心try區塊,即未發生異常的時候,對於Test1來講,IL代碼多出了8個字節來保存catch的處理代碼,這一點對性能和資源幾乎是微不足道的。

我們看到當Test1執行到IL_000f或者IL_0013的時候,將數據出棧並使用leave.s退出try區塊轉向IL_001b地址,然後將數據入棧並返回。

對於Test2來講,執行到IL_000e或者IL_0012的時候, 直接退出,並將數據入棧然後返回。

這裡對幾個關鍵指令簡單介紹一下

nop   do noting

stloc.0  Pop value from stack into local variable 0.

ldloc.0  Load local variable 0 onto stack.

br.s target  branch to target, short form

leave.s target  Exit a protected region of code, short form

下面我們看代碼的實際運行情況,新建一個控制台Console程序,加入下面代碼:

static void Main(string[] args)
{
   int times = 1000000;  //我們將結果放大100,0000倍
   long l1, l2,l3,l4, s1, s2;
   Console.WriteLine("Press any key to continue");
   Console.Read();
   for (int j = 0; j < 10; j++)
   {
     l1 = DateTime.Now.Ticks;
     for (int i = 0; i < times; i++)
       Test2(2, 4);
     l2 = DateTime.Now.Ticks;
     s1 = l2 - l1;
     Console.WriteLine("time spent:" + s1);
     l3 = DateTime.Now.Ticks;
     for (int i = 0; i < times; i++)
       Test1(2, 4);
     l4 = DateTime.Now.Ticks;
     s2 = l4 - l3;
     Console.WriteLine("time spent:" + s2);
     Console.WriteLine("difference:" + (s2 - s1) + ", rate:" + (float)(s2 - s1) / s1 / times);
   }
}
static int Test1(int a, int b)
{
   try
   {
     for (int i = 0; i < 100; i++) ; // 模擬長時操縱
     if (a > b)
       return a;
     return b;
   }
   catch
   {
     return -1;
   }
}
static int Test2(int a, int b)
{
   for (int i = 0; i < 100; i++) ; // 模擬長時操縱
   if (a > b)
     return a;
   return b;
}

運行後可以看到代碼的差異,通常在0.0001%的差別以內。

第二點:如果發生異常,那麼引發或處理異常時,將使用大量的系統資源和執行時間。引發異常只是為了處理確實異常的情況,而不是為了處理可預知的事件或流控制。例如,如果方法參數無效,而應用程序需要使用有效的參數調用方法,則可以引發異常。無效的方法參數意味著出現了異常情況。相反,用戶偶爾會輸入無效數據,這是可以預見的,因此如果用戶輸入無效,則不要引發異常。在這種情況下,請提供重試機制以便用戶輸入有效輸入。

我們經常需要將一個字符串轉換為int,比如將Request.QueryString["id"]這樣的字符串轉換為int,在asp.net 1.x時代,我們常使用下列方式:

try
{
   int id = Int32.Parse("123");
}
catch(){}

這樣的後果是如果出現轉換異常,你將不得不犧牲大量的系統資源來處理異常,即使你沒有編寫任何異常處理代碼。

當然你也可以編寫大量的代碼來檢測和轉換字符串來替代try/catch方式,而從asp.net 2.0以後,框架將這個檢測轉換過程封裝到Int32.TryParse方法中,再也不用蹩腳的try/catch來處理了。

還要補充一點,就是finally中的代碼是始終保證運行的,所以留給大家一個問題,下面代碼執行後a的值是多少:

int a = 2;
try
{
   int i = Int32.Parse("s");
}
catch
{
   a = 1;
   return;
}
finally
{
   a = 3;
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved