您的位置:首页 > 编程语言 > C#

C#中的异常处理

2008-01-17 10:32 267 查看
通用语言运行时(CLR)具有的一个很大的优势,异常处理是跨语言被标准化的。一个在C#中所引发的异常可以在Visual Basic客户中得到处理。不再有 HRESULTs 或者 ISupportErrorInfo 接口。

尽管跨语言异常处理的覆盖面很广,你稍为改变编译器的溢出处理行为,接着有趣的事情就开始了:你处理了该异常。要增加更多的手段,随后引发你所创建的异常。

1.1 校验(checked)和非校验(unchecked)语句

当你执行运算时,有可能会发生计算结果超出结果变量数据类型的有效范围。这种情况被称为溢出,依据不同的编程语言,你将被以某种方式通知——或者根本就没有被通知。

那么,C#如何处理溢出的呢? 要找出其默认行为,请看下面的阶乘的例子。

example 1.1 计算一个数的阶乘

1: using System;

2:

3: class Factorial

当你象这样使用命令行执行程序时

factorial 2000

结果为0,什么也没有发生。因此,设想C#默默地处理溢出情况而不明确地警告你是安全的。

通过给整个应用程序(经编译器开关)或于语句级允许溢出校验,你就可以改变这种行为。以下两节分别解决一种方

案。

1.1.1 给溢出校验设置编译器

如果你想给整个应用程序控制溢出校验,C#编译器设置选择是正是你所要找的。默认地,溢出校验是禁用的。要明确

地要求它,运行以下编译器命令:

csc factorial.cs /checked+

现在当你用2000参数执行应用程序时,CLR通知你溢出异常。

允许了溢出异常,阶乘代码产生了一个异常。

  按OK键离开对话框揭示了异常信息:

Exception occurred: System.OverflowException

at Factorial.Main(System.String[])

  现在你了解了溢出条件引发了一个 System.OverflowException异常。

1.1.2 语法溢出校验

  如果你不想给整个应用程序允许溢出校验,仅给某些代码段允许校验,你可能会很舒适。对于这种场合,你可能象清

单1.2中显示的那样,使用校验语句。

example 1.2  阶乘计算中的溢出校验

1: using System;

2:

3: class Factorial

  甚至就如你运用标志 checked-编译了该代码,在第13行中,溢出校验仍然会对乘法实现检查。错误信息保持一致。

  显示相反行为的语句是非校验(unchecked )。甚至如果允许了溢出校验(给编译器加上checked+标志),被

unchecked 语句所括住的代码也将不会引发溢出异常:

unchecked

{

nFactorial *= nCurDig;

}

1.2  异常处理语句  

The following three sections introduce C#'s exception-handling statements:

以下三节介绍了C#的异常处理语句:

。用 try-catch 捕获异常

。用try-finally 清除异常

。用try-catch-finally 处理所有的异常

1.2.1  使用 try 和 catch捕获异常

  你肯定会对一件事非常感兴趣——不要提示给用户那令人讨厌的异常消息,以便你的应用程序继续执行。要这样,你

必须捕获(处理)该异常。

这样使用的语句是try 和 catch。try包含可能会产生异常的语句,而catch处理一个异常,如果有异常存在的话。

example 1.3 捕获由Factorial Calculation引发的OverflowException 异常

1: using System;

2:

3: class Factorial

为了说明清楚,我扩展了某些代码段,而且我也保证异常是由checked 语句产生的,甚至当你忘记了编译器设置时。

正如你所见,异常处理并不麻烦。你所有要做的是:在try语句中包含容易产生异常的代码,接着捕获异常,该异常在

这个例子中是OverflowException类型。无论一个异常什么时候被引发,在catch段里的代码会注意进行适当的处理。

如果你不事先知道哪一种异常会被预期,而仍然想处于安全状态,简单地忽略异常的类型。

try

{

...

}

catch

{

...

}

但是,通过这个途径,你不能获得对异常对象的访问,而该对象含有重要的出错信息。一般化异常处理代码象这样:

try

{

...

}

catch(System.Exception e)

{

...

}

注意,你不能用ref或out 修饰符传递 e 对象给一个方法,也不能赋给它一个不同的值。

1.2.2 使用 try 和 finally 清除异常

如果你更关心清除而不是错误处理, try 和 finally 会获得你的喜欢。它不仅抑制了出错消息,而且所有包含在

finally 块中的代码在异常被引发后仍然会被执行。

尽管程序不正常终止,但你还可以为用户获取一条消息,如example 1.4 所示。

example 1.4 在finally 语句中处理异常

1: using System;

2:

3: class Factorial

通过检测该代码,你可能会猜到,即使没有引发异常处理,finally也会被执行。这是真的——在finally中的代码总

是会被执行的,不管是否具有异常条件。为了举例说明如何在两种情况下提供一些有意义的信息给用户, 我引进了新变量

bAllFine。bAllFine告诉finally 语段,它是否是因为一个异常或者仅是因为计算的顺利完成而被调用。

作为一个习惯了SEH程序员,你可能会想,是否有一个与__leave 语句等价的语句,该语句在C++中很管用。如果你还

不了解,在C++中的__leave 语句是用来提前终止 try 语段中的执行代码,并立即跳转到finally 语段 。

坏消息, C# 中没有__leave 语句。但是,在example 1.5 中的代码演示了一个你可以实现的方案。

example 1.5 从 try语句 跳转到finally 语句

1: using System;

2:

3: class JumpTest

当这个应用程序运行时,输出结果为

try

finally

__leave

一个 goto 语句不能退出 一个finally 语段。甚至把 goto 语句放在 try 语句 段中,还是会立即返回控制到

finally 语段。因此,goto 只是离开了 try 语段并跳转到finally 语段。直到 finally 中的代码完成运行后,才能到达

__leave 标签。按这种方式,你可以模仿在SEH中使用的的__leave 语句。

顺便地,你可能怀疑goto 语句被忽略了,因为它是try 语句中的最后一条语句,并且控制自动地转移到了

finally 。为了证明不是这样,试把goto 语句放到Console.WriteLine 方法调用之前。尽管由于不可到达代码你得到了编

译器的警告,但是你将看到goto语句实际上被执行了,且没有为 try 字符串产生的输出。

1.2.3 使用try-catch-finally处理所有异常

应用程序最有可能的途径是合并前面两种错误处理技术——捕获错误、清除并继续执行应用程序。所有你要做的是在

出错处理代码中使用 try 、catch 和 finally语句。example 1.6 显示了处理零除错误的途径。

example 1.6 实现多个catch 语句

1: using System;

2:

3: class CatchIT

这个例子的技巧为,它包含了多个catch 语句。第一个捕获了更可能出现的DivideByZeroException异常,而第二个

catch语句通过捕获普通异常处理了所有剩下来的异常。

你肯定总是首先捕获特定的异常,接着是普通的异常。如果你不按这个顺序捕获异常,会发生什么事呢?example 1.7中的

代码有说明。

example 1.7 顺序不适当的 catch 语句

1: try

6: catch(Exception Ex)

10: catch(DivideByZeroException divEx)

1: try

9: catch (OverflowException oe)

1: using System;

2:

3: public class MyImportantException:Exception

14:

15: public class ExceptionTestApp

16: {

{

{

{

{

30: Console.WriteLine(e);

31: }

32: }

33: }

正如你所看到的,MyImportantException 异常类不能实现任何特殊的功能,但它完全基于System.Exception类。程序

的剩余部分测试新的异常类,给System.Exception 类使用一个catch 语句。

如果没有特殊的实现而只是给MyImportantException定义了三个构造函数,创建它又有什么意义呢?它是一个重要的

类型——你可以在catch语句中使用它,代替更为普通的异常类。可能引发你的新异常的客户代码可以按规定的catch代码

发挥作用。

当使用自己的名字空间编写一个类库时,也要把异常放到该名字空间。尽管它并没有出现在这个例子中,你还是应该

使用适当的属性,为扩展了的错误信息扩充你的异常类。

1.4 异常处理的“要”和“不要”

作为最后的忠告之语,这里是对异常引发和处理所要做和不要做的清单:

。当引发异常时,要提供有意义的文本。

。要引发异常仅当条件是真正异常;也就是当一个正常的返回值不满足时。

。如果你的方法或属性被传递一个坏参数,要引发一个ArgumentException异常。

。当调用操作不适合对象的当前状态时,要引发一个 InvalidOperationException异常。

。要引发最适合的异常。

。要使用链接异常,它们允许你跟踪异常树。

。不要为正常或预期的错误使用异常。

。不要为流程的正常控制使用异常。

。不要在方法中引发 NullReferenceException或IndexOutOfRangeException异常。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: