zoukankan      html  css  js  c++  java
  • 关于.NET的异常处理的几个误区

    作者:Truly
    日期:2007.8.5

    很久前就想写这么一篇文章,因为很多人使用.Net多年之后还是对异常处理一知半解的,有很多误解,本文将讲解三个常见误解,一个是catch的使用方法是否正确,另外两个是try/catch的性能损失问题。

    有些人认为下面代码就是一个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程序,加入下面代码:

       点击左边图标展开代码

    运行后可以看到代码的差异,通常在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 = 2;
    try
    {
        int i = Int32.Parse("s")
    ;
    }
    catch
    {
        a 
    = 1;
        return;
    }
    finally
    {
        a 
    = 3;
    }



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

    参考文献

    http://msdn2.microsoft.com/zh-cn/library/system.exception(VS.80).aspx
    http://msdn.microsoft.com/library/chs/default.asp?url=/library/CHS/cpguide/html/cpconexceptionsoverview.asp
    《CIL Instruction Set Specification》
    《Applied Microsoft.NET Framework Programming》

  • 相关阅读:
    PAT 1010. 一元多项式求导 (25)
    PAT 1009. 说反话 (20) JAVA
    PAT 1009. 说反话 (20)
    PAT 1007. 素数对猜想 (20)
    POJ 2752 Seek the Name, Seek the Fame KMP
    POJ 2406 Power Strings KMP
    ZOJ3811 Untrusted Patrol
    Codeforces Round #265 (Div. 2) 题解
    Topcoder SRM632 DIV2 解题报告
    Topcoder SRM631 DIV2 解题报告
  • 原文地址:https://www.cnblogs.com/chenbg2001/p/1795142.html
Copyright © 2011-2022 走看看