c#

位置:IT落伍者 >> c# >> 浏览文章

.NET的异常处理的几个误区


发布日期:2018年06月21日
 
.NET的异常处理的几个误区

Net出现多年之后还是对异常处理一知半解的有很多误解本文将讲解三个常见误解一个是catch的使用方法是否正确另外两个是try/catch的性能损失问题

有些人认为下面代码就是一个catch的错误用法

catch(Exception e)  

  {  

      throw e;  

  }  

首先说明这不是一个错误用法但是通常来讲我们应该避免这种代码然后要说明的是这段代码有一个比较典型的作用就是改变异常出现的位置也就是可以对某类异常统一在一个位置处理先看下面代码

public int GetAllCount()  

      {  

          try  

          {  

              openDB();  

              int i = ;  

              return i;  

          }  

          catch (SqlException sex)  

          {  

              throw sex;  

          }  

          catch (Exception ex)  

          {  

              throw ex;  

          }  

      }  

      public int GetAllCount()  

      {  

          openDB(); // 这里也可能是微软企业类库等  

          int i = ;  

          return i;  

      }  

   

      private void openDB()  

      {  

          connOpen();  

      }  

假设我们有一个公用方法叫openDB()而很多方法中调用它当数据库打开失败的时候对于调用GetAllCount方法异常将定位于 connOpen而如果调用GetAllCount那么异常定位于throw sex的位置同时堆栈信息也有所不同可以更快捷的找到调用方法的位置也可在此位置进行一些错误恢复处理尤其是我们编写一些底层类库的时候比如 Framework类库从不会把异常代码定位到Framework类库内部的某个方法上面但是需要注意的是我们尽量避免捕获异常而不返回例如 catch(){}

这样的使用就是典型的错误使用了因为对于Framework来讲任何时候系统都可能抛出一个StackOverflowException或者OutOfMemoryExcetpion而上面这段代码则隐藏了这些异常有时候则导致一些严重的问题

对于异常处理在性能上有点注意

第一点在使用try/catch时如果不发生异常那么几乎可以忽略性能的损失

关于这一点这里我们进行一些深入分析对此比较了解的可以跳过本节首先让我们先看一下try/catch的IL表现我们有个方法一个使用try/catch而另一个未做任何处理

static int Test(int a int b)  

  {  

      try  

      {  

          if (a > b)  

              return a;  

          return b;  

      }  

      catch  

      {  

          return ;  

      }  

  }  

   

  static int Test(int a int b)  

  {  

      if (a > b)  

          return a;  

      return b;  

  }  

使用ILDasm工具查看IL代码分别如下(这里之所以引入IL是因为IL是比较接近机器汇编所以在IL中我们可以更清楚的了解代码的执行情况对IL没有兴趣的可以跳过此节)

thod private hidebysig static int  Test(int a  

                                                int b) cil managed  

  {  

    // 代码大小       (xe)  

    maxstack    

    locals init ([] int CS$$  

             [] bool CS$$)  

    IL_:  nop  

    try  

    {  

      IL_:  nop  

      IL_:  ldarg  

      IL_:  ldarg  

      IL_:  cgt  

      IL_:  ldci  

      IL_:  ceq  

      IL_:  stloc  

      IL_a:  ldloc  

      IL_b:  brtrues   IL_  

      IL_d:  ldarg  

      IL_e:  stloc  

      IL_f:  leaves    IL_b  

      IL_:  ldarg  

      IL_:  stloc  

      IL_:  leaves    IL_b  

    }  // end try  

    catch [mscorlib]SystemObject   

    {  

      IL_:  pop  

      IL_:  nop  

      IL_:  ldcim  

      IL_:  stloc  

      IL_:  leaves    IL_b  

    }  // end handler  

    IL_b:  nop  

    IL_c:  ldloc  

    IL_d:  ret  

  } // end of method Program::Test  

   

  Test  

   

thod private hidebysig static int  Test(int a  

                                                int b) cil managed  

  {  

    // 代码大小       (x)  

    maxstack    

    locals init ([] int CS$$  

             [] bool CS$$)  

    IL_:  nop  

    IL_:  ldarg  

    IL_:  ldarg  

    IL_:  cgt  

    IL_:  ldci  

    IL_:  ceq  

    IL_:  stloc  

    IL_:  ldloc  

    IL_a:  brtrues   IL_  

    IL_c:  ldarg  

    IL_d:  stloc  

    IL_e:  brs       IL_  

    IL_:  ldarg  

    IL_:  stloc  

    IL_:  brs       IL_  

    IL_:  ldloc  

    IL_:  ret  

  } // end of method Program::Test  

这里我们只需关注红字高亮的几行即可此处我们只关心try区块即未发生异常的时候对于Test来讲IL代码多出了个字节来保存catch的处理代码这一点对性能和资源几乎是微不足道的

我们看到当Test执行到IL_f或者IL_的时候将数据出栈并使用leaves退出try区块转向IL_b地址然后将数据入栈并返回

对于Test来讲执行到IL_e或者IL_的时候 直接退出并将数据入栈然后返回

这里对几个关键指令简单介绍一下

nop      do noting  

  stloc  Pop value from stack into local variable   

  ldloc  Load local variable onto stack  

  brs target branch to target short form  

  leaves target Exit a protected region of code short form  

下面我们看代码的实际运行情况新建一个控制台Console程序加入下面代码

static void Main(string[] args)  

  {  

      int times = ;    //我们将结果放大倍  

      long l lll s s;  

   

      ConsoleWriteLine(Press any key to continue);  

      ConsoleRead();  

   

      for (int j = ; j < ; j++)  

      {  

          l = DateTimeNowTicks;  

   

          for (int i = ; i < times; i++)  

              Test( );  

   

          l = DateTimeNowTicks;  

          s = l l;  

          ConsoleWriteLine(time spent: + s);  

   

          l = DateTimeNowTicks;  

   

          for (int i = ; i < times; i++)  

              Test( );  

   

          l = DateTimeNowTicks;  

          s = l l;  

          ConsoleWriteLine(time spent: + s);  

     ConsoleWriteLine(difference:+(ss)+rate:+(float)(ss)/s/times);       }  

  }  

   

  static int Test(int a int b)  

  {  

      try  

      {  

          for (int i = ; i < ; i++) ;  // 模拟长时操纵  

          if (a > b)  

              return a;  

          return b;  

      }  

      catch  

      {  

          return ;  

      }  

  }  

   

  static int Test(int a int b)  

  {  

      for (int i = ; i < ; i++) ;  // 模拟长时操纵  

      if (a > b)  

          return a;  

      return b;  

  }  

运行后可以看到代码的差异通常在%的差别以内

第二点如果发生异常那么引发或处理异常时将使用大量的系统资源和执行时间引发异常只是为了处理确实异常的情况而不是为了处理可预知的事件或流控制例如如果方法参数无效而应用程序需要使用有效的参数调用方法则可以引发异常无效的方法参数意味着出现了异常情况相反用户偶尔会输入无效数据这是可以预见的因此如果用户输入无效则不要引发异常在这种情况下请提供重试机制以便用户输入有效输入

我们经常需要将一个字符串转换为int比如将RequestQueryString[id]这样的字符串转换为intx时代我们常使用下列方式

try  

  {  

      int id = IntParse();  

  }  

  catch(){}  

这样的后果是如果出现转换异常你将不得不牺牲大量的系统资源来处理异常即使你没有编写任何异常处理代码

当然你也可以编写大量的代码来检测和转换字符串来替代try/catch方式而从 以后框架将这个检测转换过程封装到IntTryParse方法中再也不用蹩脚的try/catch来处理了

还要补充一点就是finally中的代码是始终保证运行的所以留给大家一个问题下面代码执行后a的值是多少

int a = ;  

  try  

  {  

      int i = IntParse(s);  

  }  

  catch  

  {  

      a = ;  

      return;  

  }  

  finally  

  {  

      a = ;  

  }  

小节本文主要对异常处理的个常见误解进行了纠正撰稿仓促如有疏漏烦请指出

               

上一篇:C#基本语法

下一篇:利用Visual C# 2005制作简单动画效果