分享
 
 
 

《.net编程先锋C#》第七章 异常处理

王朝c#·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

《.net编程先锋C#》第七章 异常处理

Christoph Wille|2001-9-3

第七章 异常处理

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

尽管跨语言异常处理的覆盖面很广,但这一章完全集中讨论C#异常处理。你稍为改变编译器的溢出处理行为,接着有趣的事情就开始了:你处理了该异常。要增加更多的手段,随后引发你所创建的异常。</P><P>7.1 校验(checked)和非校验(unchecked)语句

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

那么,C#如何处理溢出的呢? 要找出其默认行为,请看我在这本书前面提到的阶乘的例子。(为了方便其见,前面的例子再次在清单 7.1 中给出)</P><P>清单 7.1 计算一个数的阶乘</P><P>1: using System;

2:

3: class Factorial

4: {

5: public static void Main(string[] args)

6: {

7: long nFactorial = 1;

8: long nComputeTo = Int64.Parse(args[0]);

9:

10: long nCurDig = 1;

11: for (nCurDig=1;nCurDig <= nComputeTo; nCurDig++)

12: nFactorial *= nCurDig;

13:

14: Console.WriteLine("{0}! is {1}",nComputeTo, nFactorial);

15: }

16: }</P><P>当你象这样使用命令行执行程序时

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

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

7.1.1 给溢出校验设置编译器

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

csc factorial.cs /checked+</P><P>现在当你用2000参数执行应用程序时,CLR通知你溢出异常(见图 7.1)。</P><P>图 7.1允许了溢出异常,阶乘代码产生了一个异常。</P><P>按OK键离开对话框揭示了异常信息:

Exception occurred: System.OverflowException

at Factorial.Main(System.String[])</P><P>现在你了解了溢出条件引发了一个 System.OverflowException异常。下一节,在我们完成语法校验之后,如何捕获并处理所出现的异常?

7.1.2语法溢出校验

如果你不想给整个应用程序允许溢出校验,仅给某些代码段允许校验,你可能会很舒适。对于这种场合,你可能象清单7.2中显示的那样,使用校验语句。</P><P>清单 7.2阶乘计算中的溢出校验</P><P>1: using System;

2:

3: class Factorial

4: {

5: public static void Main(string[] args)

6: {

7: long nFactorial = 1;

8: long nComputeTo = Int64.Parse(args[0]);

9:

10: long nCurDig = 1;

11:

12: for (nCurDig=1;nCurDig <= nComputeTo; nCurDig++)

13: checked { nFactorial *= nCurDig; }

14:

15: Console.WriteLine("{0}! is {1}",nComputeTo, nFactorial);

16: }

17: }</P><P>甚至就如你运用标志 checked-编译了该代码,在第13行中,溢出校验仍然会对乘法实现检查。错误信息保持一致。</P><P>显示相反行为的语句是非校验(unchecked )。甚至如果允许了溢出校验(给编译器加上checked+标志),被unchecked 语句所括住的代码也将不会引发溢出异常:</P><P>unchecked

{

nFactorial *= nCurDig;

}</P><P></P><P>7.2 异常处理语句

既然你知道了如何产生一个异常(你会发现更多的方法,相信我),仍然存在如何处理它的问题。如果你是一个 C++ WIN32 程序员,肯定熟悉SEH(结构异常处理)。你将从中找到安慰,C#中的命令几乎是相同的,而且它们也以相似的方式运作。</P><P>The following three sections introduce C#'s exception-handling statements:

以下三节介绍了C#的异常处理语句:</P><P>。用 try-catch 捕获异常

。用try-finally 清除异常

。用try-catch-finally 处理所有的异常</P><P>7.2.1 使用 try 和 catch捕获异常

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

这样使用的语句是try 和 catch。try包含可能会产生异常的语句,而catch处理一个异常,如果有异常存在的话。清单7.3 用try 和 catch为OverflowException 实现异常处理。</P><P>清单7.3 捕获由Factorial Calculation引发的OverflowException 异常</P><P>1: using System;

2:

3: class Factorial

4: {

5: public static void Main(string[] args)

6: {

7: long nFactorial = 1, nCurDig=1;

8: long nComputeTo = Int64.Parse(args[0]);

9:

10: try

11: {

12: checked

13: {

14: for (;nCurDig <= nComputeTo; nCurDig++)

15: nFactorial *= nCurDig;

16: }

17: }

18: catch (OverflowException oe)

19: {

20: Console.WriteLine("Computing {0} caused an overflow exception", nComputeTo);

21: return;

22: }

23:

24: Console.WriteLine("{0}! is {1}",nComputeTo, nFactorial);

25: }

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

正如你所见,异常处理并不麻烦。你所有要做的是:在try语句中包含容易产生异常的代码,接着捕获异常,该异常在这个例子中是OverflowException类型。无论一个异常什么时候被引发,在catch段里的代码会注意进行适当的处理。

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

{

...

}

catch

{

...

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

{

...

}

catch(System.Exception e)

{

...

}</P><P>注意,你不能用ref或out 修饰符传递 e 对象给一个方法,也不能赋给它一个不同的值。</P><P>7.2.2 使用 try 和 finally 清除异常

如果你更关心清除而不是错误处理, try 和 finally 会获得你的喜欢。它不仅抑制了出错消息,而且所有包含在 finally 块中的代码在异常被引发后仍然会被执行。

尽管程序不正常终止,但你还可以为用户获取一条消息,如清单 7.4 所示。</P><P>清单 7.4 在finally 语句中处理异常</P><P>1: using System;

2:

3: class Factorial

4: {

5: public static void Main(string[] args)

6: {

7: long nFactorial = 1, nCurDig=1;

8: long nComputeTo = Int64.Parse(args[0]);

9: bool bAllFine = false;

10:

11: try

12: {

13: checked

14: {

15: for (;nCurDig <= nComputeTo; nCurDig++)

16: nFactorial *= nCurDig;

17: }

18: bAllFine = true;

19: }

20: finally

21: {

22: if (!bAllFine)

23: Console.WriteLine("Computing {0} caused an overflow exception", nComputeTo);

24: else

25: Console.WriteLine("{0}! is {1}",nComputeTo, nFactorial);

26: }

27: }

28: }</P><P>通过检测该代码,你可能会猜到,即使没有引发异常处理,finally也会被执行。这是真的——在finally中的代码总是会被执行的,不管是否具有异常条件。为了举例说明如何在两种情况下提供一些有意义的信息给用户, 我引进了新变量bAllFine。bAllFine告诉finally 语段,它是否是因为一个异常或者仅是因为计算的顺利完成而被调用。

作为一个习惯了SEH程序员,你可能会想,是否有一个与__leave 语句等价的语句,该语句在C++中很管用。如果你还不了解,在C++中的__leave 语句是用来提前终止 try 语段中的执行代码,并立即跳转到finally 语段 。

坏消息, C# 中没有__leave 语句。但是,在清单 7.5 中的代码演示了一个你可以实现的方案。</P><P>清单 7.5 从 try语句 跳转到finally 语句</P><P>1: using System;

2:

3: class JumpTest

4: {

5: public static void Main()

6: {

7: try

8: {

9: Console.WriteLine("try");

10: goto __leave;

11: }

12: finally

13: {

14: Console.WriteLine("finally");

15: }

16:

17: __leave:

18: Console.WriteLine("__leave");

19: }

20: }</P><P>

当这个应用程序运行时,输出结果为</P><P>try

finally

__leave</P><P>一个 goto 语句不能退出 一个finally 语段。甚至把 goto 语句放在 try 语句 段中,还是会立即返回控制到 finally 语段。因此,goto 只是离开了 try 语段并跳转到finally 语段。直到 finally 中的代码完成运行后,才能到达__leave 标签。按这种方式,你可以模仿在SEH中使用的的__leave 语句。

顺便地,你可能怀疑goto 语句被忽略了,因为它是try 语句中的最后一条语句,并且控制自动地转移到了 finally 。为了证明不是这样,试把goto 语句放到Console.WriteLine 方法调用之前。尽管由于不可到达代码你得到了编译器的警告,但是你将看到goto语句实际上被执行了,且没有为 try 字符串产生的输出。</P><P>7.2.3 使用try-catch-finally处理所有异常

应用程序最有可能的途径是合并前面两种错误处理技术——捕获错误、清除并继续执行应用程序。所有你要做的是在出错处理代码中使用 try 、catch 和 finally语句。清单 7.6 显示了处理零除错误的途径。</P><P>清单 7.6 实现多个catch 语句</P><P>1: using System;

2:

3: class CatchIT

4: {

5: public static void Main()

6: {

7: try

8: {

9: int nTheZero = 0;

10: int nResult = 10 / nTheZero;

11: }

12: catch(DivideByZeroException divEx)

13: {

14: Console.WriteLine("divide by zero occurred!");

15: }

16: catch(Exception Ex)

17: {

18: Console.WriteLine("some other exception");

19: }

20: finally

21: {

22: }

23: }

24: }</P><P>这个例子的技巧为,它包含了多个catch 语句。第一个捕获了更可能出现的DivideByZeroException异常,而第二个catch语句通过捕获普通异常处理了所有剩下来的异常。

你肯定总是首先捕获特定的异常,接着是普通的异常。如果你不按这个顺序捕获异常,会发生什么事呢?清单7.7中的代码有说明。</P><P>清单7.7 顺序不适当的 catch 语句</P><P>1: try

2: {

3: int nTheZero = 0;

4: int nResult = 10 / nTheZero;

5: }

6: catch(Exception Ex)

7: {

8: Console.WriteLine("exception " + Ex.ToString());

9: }

10: catch(DivideByZeroException divEx)

11: {

12: Console.WriteLine("never going to see that");

13: }</P><P>

编译器将捕获到一个小错误,并类似这样报告该错误:

wrongcatch.cs(10,9): error CS0160: A previous catch clause already

catches all exceptions of this or a super type ('System.Exception')</P><P>最后,我必须告发CLR异常与SEH相比时的一个缺点(或差别):没有 EXCEPTION_CONTINUE_EXECUTION标识符的等价物,它在SEH异常过滤器中很有用。基本上,EXCEPTION_CONTINUE_EXECUTION 允许你重新执行负责异常的代码片段。在重新执行之前,你有机会更改变量等。我个人特别喜欢的技术为,使用访问违例异常,按需要实施内存分配。</P><P>

7.3 引发异常

当你必须捕获异常时,其他人首先必须首先能够引发异常。而且,不仅其他人能够引发,你也可以负责引发。其相当简单:</P><P>throw new ArgumentException("Argument can't be 5");

你所需要的是throw 语句和一个适当的异常类。我已经从表7.1提供的清单中选出一个异常给这个例子。</P><P>表 7.1 Runtime提供的标准异常</P><P>

异常类型 描述</P><P>Exception 所有异常对象的基类

SystemException 运行时产生的所有错误的基类

IndexOutOfRangeException 当一个数组的下标超出范围时运行时引发

NullReferenceException 当一个空对象被引用时运行时引发

InvalidOperationException 当对方法的调用对对象的当前状态无效时,由某些方法引发

ArgumentException 所有参数异常的基类

ArgumentNullException 在参数为空(不允许)的情况下,由方法引发

ArgumentOutOfRangeException 当参数不在一个给定范围之内时,由方法引发

InteropException 目标在或发生在CLR外面环境中的异常的基类

ComException 包含COM 类的HRESULT信息的异常

SEHException 封装win32 结构异常处理信息的异常</P><P>然而,在catch语句的内部,你已经有了随意处置的异常,就不必创建一个新异常。可能在表7.1 中的异常没有一个符合你特殊的要求——为什么不创建一个新的异常?在即将要学到小节中,都涉及到这两个话题。</P><P>7.3.1 重新引发异常

当处于一个catch 语句的内部时,你可能决定引发一个目前正在再度处理的异常,留下进一步的处理给一些外部的try-catch 语句。该方法的例子如 清单7.8所示。</P><P>清单 7.8 重新引发一个异常</P><P>1: try

2: {

3: checked

4: {

5: for (;nCurDig <= nComputeTo; nCurDig++)

6: nFactorial *= nCurDig;

7: }

8: }

9: catch (OverflowException oe)

10: {

11: Console.WriteLine("Computing {0} caused an overflow exception", nComputeTo);

12: throw;

13: }</P><P>注意,我不必规定所声明的异常变量。尽管它是可选的,但你也可以这样写:

throw oe;

现在有时还必须留意这个异常。</P><P>7.3.2 创建自己的异常类

尽管建议使用预定义的异常类,但对于实际场合,创建自己的异常类可能会方便。创建自己的异常类,允许你的异常类的使用者根据该异常类采取不同的手段。

在清单 7.9 中出现的异常类 MyImportantException遵循两个规则:第一,它用Exception结束类名。第二,它实现了所有三个被推荐的通用结构。你也应该遵守这些规则。

清单 7.9 实现自己的异常类 MyImportantException</P><P>1: using System;

2:

3: public class MyImportantException:Exception

4: {

5: public MyImportantException()

6: :base() {}

7:

8: public MyImportantException(string message)

9: :base(message) {}

10:

11: public MyImportantException(string message, Exception inner)

12: :base(message,inner) {}

13: }

14:

15: public class ExceptionTestApp

16: {

17: public static void TestThrow()

18: {

19: throw new MyImportantException("something bad has happened.");

20: }

21:

22: public static void Main()

23: {

24: try

25: {

26: ExceptionTestApp.TestThrow();

27: }

28: catch (Exception e)

29: {

30: Console.WriteLine(e);

31: }

32: }

33: }</P><P>正如你所看到的,MyImportantException 异常类不能实现任何特殊的功能,但它完全基于System.Exception类。程序的剩余部分测试新的异常类,给System.Exception 类使用一个catch 语句。

如果没有特殊的实现而只是给MyImportantException定义了三个构造函数,创建它又有什么意义呢?它是一个重要的类型——你可以在catch语句中使用它,代替更为普通的异常类。可能引发你的新异常的客户代码可以按规定的catch代码发挥作用。

当使用自己的名字空间编写一个类库时,也要把异常放到该名字空间。尽管它并没有出现在这个例子中,你还是应该使用适当的属性,为扩展了的错误信息扩充你的异常类。</P><P>7.4 异常处理的“要”和“不要”

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

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

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

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

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

。要引发最适合的异常。

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

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

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

。不要在方法中引发 NullReferenceException或IndexOutOfRangeException异常。</P><P>7.5 小结

这一章由介绍溢出校验开始。你可以使用编译器开关(默认是关),使整个应用程序允许或禁止溢出校验。如果需要微调控制,你可以使用校验和非校验语句,它允许你使用或不使用溢出校验来执行一段代码,尽管没有给应用程序设置开关。

当发生溢出时,一个异常就被引发了。如何处理异常取决于你。我提出了各种途径,包括你最有可能贯穿整个应用程序使用的:try、catch 和finally 语句。在伴随的多个例子中,你学到了它与WIN32结构异常处理(SEH)的差别。

异常处理是给类的用户; 然而,如果你负责创建新的类,就可以引发异常。有多种选择:引发早已捕获的异常,引发存在的框架异常,或者按规定的实际目标创建新的异常类。

最后,你需要阅读引发和处理异常的各种“要”和“不要”。</P><P>

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有