关于.NET的异常处理的几个误区
来源:互联网 发布:新青山软件 编辑:程序博客网 时间:2024/06/15 14:02
有些人认为下面代码就是一个catch的错误用法:
{
throw e;
}
首先说明,这不是一个错误用法,但是通常来讲,我们应该避免这种代码。然后要说明的是,这段代码有一个比较典型的作用就是改变异常出现的位置,也就是可以对某类异常统一在一个位置处理。先看下面代码:
{
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类库内部的某个方法上面。但是需要注意的是我们尽量避免捕获异常而不返回,例如
这样的使用就是典型的错误使用了,因为对于Framework来讲,任何时候系统都可能抛出一个StackOverflowException或者OutOfMemoryExcetpion而上面这段代码则隐藏了这些异常,有时候则导致一些严重的问题。
对于异常处理,在性能上有2点注意
第一点,在使用try/catch时,如果不发生异常,那么几乎可以忽略性能的损失。
关于这一点,这里我们进行一些深入分析,对此比较了解的可以跳过本节。首先,让我们先看一下try/catch的IL表现。我们有2个方法,一个使用try/catch,而另一个未做任何处理:
{
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没有兴趣的可以跳过此节)
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
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程序,加入下面代码:
![](http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
static void Main(string[] args)
{
int times = 1000000; //我们将结果放大100,0000倍
long l1, l2,l3,l4, s1, s2;
Console.WriteLine("Press any key to continue
![](http://www.cnblogs.com/Images/dot.gif)
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时代,我们常使用下列方式
{
int id = Int32.Parse("123");
}
catch(){}
这样的后果是如果出现转换异常,你将不得不牺牲大量的系统资源来处理异常,即使你没有编写任何异常处理代码。
当然你也可以编写大量的代码来检测和转换字符串来替代try/catch方式,而从asp.net 2.0以后,框架将这个检测转换过程封装到Int32.TryParse方法中,再也不用蹩脚的try/catch来处理了。
还要补充一点,就是finally中的代码是始终保证运行的,所以留给大家一个问题,下面代码执行后a的值是多少:
try
{
int i = Int32.Parse("s");
}
catch
{
a = 1;
return;
}
finally
{
a = 3;
}
小节:本文主要对异常处理的3个常见误解进行了纠正。撰稿仓促,如有疏漏,烦请指出。
- 关于.NET的异常处理的几个误区
- 关于.NET的异常处理的几个误区
- .NET的异常处理的几个误区
- .NET的异常处理的几个误区
- .NET的异常处理的几个误区
- .NET的异常处理的几个误区
- 关于继承的几个误区
- .NET中异常处理的几个建议
- 关于参加ACM的几个误区
- 关于参加ACM的几个误区
- 谈谈关于计算机科学的几个误区
- [转]关于数码摄影的几个误区
- 关于参加ACM的几个误区
- Java 异常处理的误区和经验总结
- Java 异常处理的误区和经验总结
- Java 异常处理的误区和经验总结
- Java 异常处理的误区总结
- Java 异常处理的误区和经验总结
- 自己做网页写的一些常用正则表达式—欢迎拷用并指正!
- 防止一个用户登录多次的方法
- 自我提升的十大技巧
- “家”建立了
- Grid控件高级应用
- 关于.NET的异常处理的几个误区
- 访微软收购主管:微软下一个收购目标是什么
- delphi 采用 raw socket 编写 net send 网络消息程序
- QT4:遍历删除某个目录下的所有文件
- connection string大全1
- 迁居通知
- 新家落定
- mysql的SQL_CALC_FOUND_ROWS 使用
- 调用存储过程并且使用返回值的基本方法