用户登录  |  用户注册
首 页商业源码原创产品编程论坛
当前位置:PB创新网文章中心编程技巧编程其他

<<展现C#>> 第七章 异常处理(修订)

减小字体 增大字体 作者:佚名  来源:本站整理  发布时间:2009-03-16 19:43:22

     第七章   异常处理

 

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

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

 

7.1  检查和非检查语句(checked and unchecked statements)

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

     那么,C#如何处理溢出的呢? 要找出其默认行为,请看我在这本书前面提到的阶乘的例子。(为了方便其见,前面的例子再次在清单 7.1 中给出)

 

清单 7.1     计算一个数的阶乘

 

 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: }

 

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

    factorial 2000

 

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

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

7.1.1溢出检查的编译器设置

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

csc factorial.cs /checked+

 

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

 

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

 

 

 

  按确定键离开对话框出现了异常信息:

Exception occurred: System.OverflowException

  at Factorial.Main(System.String[])

 

  现在你了解了溢出条件引发了一个 System.OverflowException异常。下一节,在我们完成语法检查之后,如何捕获并处理所出现的异常?

7.1.2 语法溢出检查

  如果你不想对整个应用程序进行溢出检查,那么只允许对某些代码段进行检查便可,这样可能会很顺畅。在这种场合下,你可能象清单7.2中显示的那样使用检查语句。

 

清单 7.2  阶乘计算中的溢出检查

 

 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: }

 

  纵使你运用标志 checked- 编译了该代码,在第13行中,溢出检查仍然会对乘法实施检查,因为checked 语句已经括住了它。将会出现相同的错误信息。

  显示相反行为的语句是unchecked 。甚至如果允许了溢出检查(给编译器加上checked+标志),被unchecked 语句所括住的代码也将不会引发溢出异常:

 

unchecked

{

 nFactorial *= nCurDig;

}

 

7.2  异常处理语句

  既然你知道了如何产生一个异常(你会发现更多的方法,相信我),仍然存在如何处理它的问题。如果你是一个 C++ WIN32 程序员,肯定熟悉SEH(结构异常处理)。令人感到欣慰的是,C#中的命令几乎是相同的,而且它们也以相似的方式运作。

 

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

。用 try-catch 捕获异常

。用try-finally 清除异常

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

 

7.2.1  使用 try catch捕获异常

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

要用到的语句是try catchtry包含可能会产生异常的语句,而catch处理一个异常,如果有异常存在的话。清单7.3 try catchOverflowException 实现异常处理。

 

清单7.3  捕获由   Factorial Calculation引发的OverflowException 异常

 

 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: }

 

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

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

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

 

try

{

...

}

catch

{

...

}

 

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

 

try

{

...

}

catch(System.Exception e)

{

...

}

 

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

 

7.2.2  使用 try finally 清除异常

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

尽管程序不正常终止,但你还可以给用户提供一条消息,如清单 7.4 所示。

 

清单 7.4  finally 语句中处理异常

 

 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: }

 

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

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

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

 

清单 7.5  try语句 跳转到finally 语句

 

 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: }

 

 

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

 

try

finally

__leave

 

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

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

 

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

应用程序最有可能的途径是合并前面两种错误处理技术――捕获错误、清除并继续执行应用程序。所有你要做的是在出错处理代码中使用 try catch finally语句。清单 7.6 显示了处理零除错误的途径。

 

清单 7.6  实现多个catch 语句

 

 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: }

 

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

你肯定总是首先捕获特定的异常,接着是普通的异常。如果你不按这个顺序捕获异常,会发生什么事呢?清单7.7中的代码有说明。

 

清单7.7   顺序不适当的 catch 语句

 

 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:   }

 

 

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

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

catches all exceptions of this or a super type ('System.Exception')

 

意思为:

wrongcatch.cs(10,9): 错误代码 CS0160: 前面的catch语句早已捕获了这个或高级类型('System.Exception')的所有异常。

 

最后,我必须报导CLR异常与SEH相比时的一个缺点(或差别):没有在SEH异常过滤器中很有用的EXCEPTION_CONTINUE_EXECUTION标识符的等价物。基本上,EXCEPTION_CONTINUE_EXECUTION 允许你重新执行负责异常的代码片段。在重新执行之前,你有机会更改变量等。我个人特别喜欢的技术为:通过使用存取违例异常,按需要实施内存分配。

  

7.3  引发异常

当你不得不捕获异常时,其他人首先必须首先能够引发异常。而且,不仅其他人能够引发,你也可以负责引发。其相当简单:

throw new ArgumentException("Argument can't be 5");

你所需要的是throw 语句和一个适合的异常类。我已经从表7.1提供的清单中为这个例子选出一个异常。

 

7.1      Runtime提供的标准异常

异常类型  

描述

Exception

所有异常对象的基类

SystemException

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

IndexOutOfRangeException

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

NullReferenceException

Tags:

作者:佚名
  • 好的评价 如果您觉得此文章好,就请您
      0%(0)
  • 差的评价 如果您觉得此文章差,就请您
      0%(0)

文章评论评论内容只代表网友观点,与本站立场无关!

   评论摘要(共 0 条,得分 0 分,平均 0 分) 查看完整评论
PB创新网ourmis.com】Copyright © 2000-2009 . All Rights Reserved .
页面执行时间:13,031.25000 毫秒
Email:ourmis@126.com QQ:2322888 蜀ICP备05006790号