第13章 异 常 处 理   异常是程序执行时遇到的任何错误情况或意外行为。应用程序必须能够统一处理在执行期间发生的错误。.NET Framework公共语言运行库提供了一个模型,以统一的方式通知程序发生的错误。本章将讲解C#语言中的异常处理机制,主要介绍以下知识点。 * 异常概述; * try语句; * System.Exception类; * .NET Framework常用异常类; * 创建自定义异常类。   通过本章学习,读者将可以了解C#语言的异常处理机制,并为应用程序发生错误时提供异常处理的方法,从而为设计容错软件提供了极大的帮助。 13.1 异 常 概 述   和C++程序语言一样,C#程序语言也存在异常处理机制。C#程序语言中的异常用于处理系统级和应用程序级的错误状态。它是一种结构化的、统一的和类型安全的处理机制。 13.1.1 导致异常的原因   在应用程序运行时,发生异常的原因是多种多样的,如除数为0、堆栈溢出等。导致异常的原因可以归纳为以下2大类。 * 在执行语句和表达式(C#语言)的过程中,有时会出现一些例外情况,从而使得某些操作无法正常完成,此时就会引发一个异常。 * 应用程序使用throw语句特意产生一个异常,此时将引发一个异常。 13.1.2 try语句   try语句可以用来捕捉在块的执行期间发生的各种异常。它可以包含try块、catch块和finally块,具体说明如下所示。 * try块:一般包含有可能发生异常的代码。 * catch块:包含出现异常时需要执行的响应代码。 * finally块:一般包含确保一定执行的代码,如资源清理操作等。   一个try语句只能有一个try块。且最多包含一个finally块。也就是说,一个try语句要么不包含finally块,要么只包含一个finally块。另外,一个try语句可以不包括catch块,也可以包括一个或多个catch块。最为常用的try语句组合的语法形式,它只包含一个try块、一个catch块和一个finally块。    try{ ///有可能发生异常的代码块 } catch{ ///当try块中的代码发生异常时,响应的代码 } finally{ ///不管try块中的代码是否发生异常,该代码块一定执行 }      【示例13-1】 创建一个类型为byte的b变量,并在try语句计算b变量和2008的和,同时使用checked语句检查这一计算操作。如果这一计算操作发生异常,则在catch块中显示异常信息。    try { byte b=100; b=checked((byte)(b+2008)); } catch(Exceptionex) { Console.WriteLine(ex.Message); ///显示异常信息 }      分析:上述语句执行之后,“b=checked((byte)(b+2008))”语句执行时发生异常,catch块会捕获该异常,并显示“算术运算导致溢出”信息。   【示例13-2】 使用try语句将“2008.1abc”字符串转换为一个整数,结果保存为i变量。该try语句包含一个try块和两个catch块。第一个catch块使用System.FormatException类捕获异常,并显示异常的信息。第二个catch块使用System.Exception类捕获异常,并显示异常的信息。    string s = "2008.1abc"; int i = -1; try { i = Int32.Parse(s); ///将s转换为整数 } catch(System.FormatException ex) { Console.WriteLine(ex.Message); ///显示异常信息 } catch(System.Exception ex) { Console.WriteLine(ex.Message); ///显示异常信息 }      分析:上述语句执行之后,“i = Int32.Parse(s);”语句执行时发生异常,第一个catch块会捕获该异常,并显示“输入字符串的格式不正确”信息。 ?注意:在上述程序代码中,如果第一个catch块没有捕获“i = Int32.Parse(s);”语句执行时的异常,那么第二个catch块将尝试捕获该语句执行时的异常。   【示例13-3】 使用try语句获取money数组(元素类型为int)的索引值为10的元素的值,结果保存为r10变量。该try语句包含一个try块、一个catch块和一个finally块。    int[] money = new int[10]; try { int r10 = money[10]; ///money数组的索引值为10的元素的值 } catch(System.Exception ex) { Console.WriteLine(ex.Message); ///显示异常信息 } finally { Console.WriteLine("Length = " + money.Length.ToString()); ///始终显示money数组的长度 }      分析:上述语句执行之后,“int r10 = money[10];”语句执行时发生异常,catch块会捕获该异常,并显示“索引超出了数组界限”信息。最后,finally语句中的块始终被执行,它显示money数组的长度。 13.1.3 异常处理   异常是由try语句处理的。当发生异常时,系统将搜索与当前try块相关的最近的、可以处理该异常的catch块,然后由该catch块处理该异常。如果与当前try块相关的所有catch块都不能处理该异常,则沿着堆栈向上搜索封闭该异常代码的try块及其相关联的catch块。如果找到,则由该catch块处理该异常。如果还没有找到catch块,则重复上述过程,直到找到catch块。如果到达调用堆栈顶部仍然没有找到处理该异常的catch块,则由默认的异常处理程序处理该异常,然后应用程序终止。 13.2 System.Exception类   System.Exception类表示在应用程序执行期间发生的错误,它是.NET Framework中所有异常的基类型。当系统或应用程序发生错误时,将通过引发包含错误信息的异常来报告错误。一旦异常发生后,将由该应用程序或默认异常处理程序(如System.Exception类)来处理 异常。   System.Exception类包含描述错误的可读文本。当异常发生时,应用程序的运行库(如.NET Framework)将产生消息(保存在Exception类的实例的Message属性中)通知用户错误的性质并提供解决该问题的操作建议。Exception类包括以下两类异常。 * 从SystemException派生的预定义公共语言运行库异常类。 * 从ApplicationException派生的用户定义的应用程序异常类。   System.Exception类包含8个属性,如描述异常原因的Message属性、导致当前异常的Exception实例的InnerException属性等。System.Exception类的常用属性及其说明如表13.1所示。 表13.1 System.Exception类的常用属性表 属 性 描 述 Message 描述当前异常的消息,即发生异常发生的原因 InnerException 导致当前异常的Exception实例 Source 导致错误的应用程序或对象的名称 Data 用户定义的其他异常信息的“键/值”对的集合 HelpLink 此当前异常相关的帮助文件的链接 HResult 分配给特定异常的编码数值(HRESULT) TargetSite 引发当前异常的方法 StackTrace 当前异常发生时,堆栈上的帧的字符串表示形式   Message和InnerException属性是System.Exception类的最常用的属性。Message属性是只读属性,它包含发生当前异常的原因,即描述异常的错误信息。通过这些信息,使得开发人员或用户能够更加清楚方便地了解发生异常的原因。InnerException属性也是一个只读属性,它表示导致当前异常的Exception实例。此时,它的值不为null。   System.Exception类包含3个方法,如获取异常基对象(一个Exception对象)的GetBaseException()方法等。System.Exception类的常用方法及其说明如表13.2所示。 表13.2 System.Exception类的常用方法表 方 法 描 述  GetBaseException() 获取异常的根Exception对象  GetObjectData() 获取与异常相关的SerializationInfo属性的值  GetType() 获取当前实例的运行时类型 13.3 常用异常类   .NET Framework提供了10个常用异常类,如处理空(null)引用的System.NullReferenceException类、处理数组索引小于0或超过其下标的System.IndexOutOf RangeException类等。这些常用异常类及其说明如表13.3所示。 表13.3 .NET Framework的常用异常类表 类 描 述 System.NullReferenceException 如果使用null引用,引发此异常 System.IndexOutOfRangeException 如果使用小于零或超出数组界限的下标访问数组,引发该异常 System.InvalidCastException 如果显式转换失败,就会引发此异常 System.DivideByZeroException 当除数为0时,引发该异常 System.StackOverflowException 当堆栈溢出(如无限递归)时,引发该异常 System.OverflowException 在checked上下文中的算术运算溢出时,引发该异常 System.OutOfMemoryException 在通过new操作分配内存失败时,引发该异常 System.ArithmeticException 在算术运算期间发生错误时,引发该异常 System.TypeInitializationException 静态构造函数引发异常,且没有可以捕捉到它的catch子句时,引发该异常 System.ArrayTypeMismatchException 如果被存储的元素的实际类型与数组的元素类型不兼容,引发此 异常   【示例13-4】 首先创建3个类型为int的变量x、y和checkedResult。x变量的值为32位最大整数,y变量的值为2,checkedResult变量保存x和y变量之和。然后在try…catch语句中、并使用checked运算符检查了x和y变量之和。由于x和y变量之和产生溢出,因此会引发OverflowException异常,并在catch语句中显示异常信息。    int x = Int32.MaxValue; ///32位最大整数 int y = 2; int checkedResult; ///用来保存使用checked检查运算的结果 Console.WriteLine("x = " + x.ToString()); Console.WriteLine("y = " + y.ToString()); try { checkedResult = checked(x + y); ///使用checked检查运算,结果保存为 checkedResult Console.WriteLine("checked(x + y) = " + checkedResult.ToString()); } catch(OverflowException oex) { Console.WriteLine(oex.Message); ///显示异常信息 }      上述程序代码运行之后,在控制台中的输出如下。    x = 2147483647 y = 2 算术运算导致溢出。 13.4 实例:自定义异常处理类   本实例创建了一个自定义异常处理类,它的名称为CustomException,并直接继承于System.Exception类。CustomException类声明了一个名称为ErrorCode的属性,表示错误代码。因此,使用CustomException类处理异常,可以为异常添加错误代码。 13.4.1 设计CustomException类   CustomException类包含1个字段:errorCode,表示错误代码,类型为int。程序代码 如下:    public class CustomException:System.Exception { private int errorCode = 10001;      CustomException类声明了1个属性:ErrorCode,用来获取或设置errorCode字段的值。程序代码如下:    /// 错误代码 public int ErrorCode { get{return errorCode;} set{errorCode = value;} }   CustomException类声明了以下4个实例构造函数,每一个构造函数都初始化了errorCode字段的值。 * public CustomException(int errorCode):调用了基类的base()构造函数,初始化错误 代码。 * public CustomException(int errorCode,string message):调用了基类的base(string message)构造函数,初始化错误代码和错误消息。 * public CustomException(int errorCode,string message,Exception innerException):调用了基类的base(string message,Exception innerException)构造函数,初始化错误代码、错误消息和异常类的实例。 * public CustomException(int errorCode,System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext conext):调用了基类的base(string message,System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serializa- tion.StreamingContext conext)构造函数,初始化错误代码、序列化数据和上下文信息。   程序代码如下:    /// 构造函数,初始化错误代码 public CustomException(int errorCode) : base() { this.errorCode = errorCode; } /// 构造函数,初始化错误代码和错误消息 public CustomException(int errorCode,string message) : base(message) { this.errorCode = errorCode; } /// 构造函数,初始化错误代码、错误消息和异常类的实例 public CustomException(int errorCode,string message,Exception innerEx ception) : base(message,innerException) { this.errorCode = errorCode; } /// 构造函数,初始化错误代码、序列化数据和上下文信息 public CustomException(int errorCode, System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext conext) :base(info,conext) { this.errorCode = errorCode; } } 13.4.2 使用CustomException类处理异常   【实例13-1】 使用CustomException类处理异常,并介绍了使用CustomException类产生异常并捕获该类产生异常的方法。实例13-1的具体实现步骤如下:   (1)在Visual Studio 2008集成开发环境中打开名称为Sample_13的控制台应用程序。   (2)右击“解决资源方案管理器”面板中的Program.cs节点,打开名称为Program.cs的类文件,并在该文件的Main(string[] args)方法中添加测试代码。具体实现步骤如下:   ① 创建类型为string的变量s,它的值为“2008.1”。   ② 创建类型为int的变量i,它的值为-1。   ③ 创建2个try语句。第二个try语句为第一个try语句的嵌套语句。第二个try语句计算将s变量转换i变量。如果产生异常,则在catch语句中使用CustomException类产生一个异常。同时,设置错误代码为10002,错误消息为“输入字符串的格式不正确”。第一个try语句将第二个try语句的所有代码放在其try块(属于第一个try语句)中,并使用catch块(属于第一个try语句)捕获其try块(属于第一个try语句)中产生的CustomException类型的异常,并显示错误代码和错误消息。 ?注意:使用CustomException类处理异常时,还添加了错误代码。   综合上述,Program.cs文件的程序代码如下:    class Program{ static void Main(string[] args) { string s = "2008.1"; int i = -1; try ///第一个try语句 { try ///第二个try语句 { i = Int32.Parse(s); ///将字符串转换为一个整数 } catch { throw new CustomException(10002,"输入字符串的格式不正确"); ///产生一个异常,添加了错误代码 } } catch(CustomException ex) { ///捕获CustomException类型的异常,并显示错误代码和错误消息 Console.WriteLine(string.Format("错误代码:{0},错误消息:{1}",ex.Error Code.ToString(),ex.Message)); } Console.ReadLine(); }}      (3)在Visual Studio 2008集成开发环境按下F5键,运行Sample_13应用程序。控制台输出的结果如下:    错误代码:10002,错误消息:输入字符串的格式不正确 13.5 上 机 实 践   1.代码调试   在Visual Studio 2008集成开发环境中调试下列代码是否能够正常运行。如果能够正常运行,请写出运行结果。如果不能正常运行,请指出错误代码,并改正。(其中,CustomException类在13.4节中定义)。    namespace Test{ class Program{ static void Main(string[] args) { string s = "2008.1"; int i = -1; try ///第一个try语句 { try ///第二个try语句 { i = Int32.Parse(s); ///将字符串转换为一个整数 } catch { throw new Exception("输入字符串的格式不正确"); ///产生一个异常,添加了错误代码 } } catch(CustomException ex) { ///捕获CustomException类型的异常,并显示错误代码和错误消息 Console.WriteLine(string.Format("错误代码:{0},错误消息:{1}",ex.Error Code.ToString(),ex.Message)); } Console.ReadLine(); }}}   2.编程题   在Visual Studio 2008集成开发环境中创建名称为MyOperationException的控制台应用程序,并在该应用程序中使用异常处理余数为0和转换字符串为整数的异常。 13.6 常见问题及解答   问题:如何抛出异常?   解答:直接使用throw和new语句,创建异常类(如Exception类等)的一个实例即可,程序代码如下:    try { int i = Int32.Parse("200812年"); ///将字符串转换为一个整数 } catch { throw new Exception("输入字符串的格式不正确"); ///产生一个异常,添加了错误代码 } 13.7 小 结   本章主要介绍了C#语言中的异常,如异常概述、try语句、System.Exception类、.NET Framework常用异常类、自定义异常类等。其中,读者要着重掌握try语句,以及处理异常的方法,为后续编写C#程序代码奠定基础。第14章将要介绍C#语言中的泛型。 13.8 习 题   在Visual Studio 2008集成开发环境中创建名称为Test_13的控制台应用程序,并实现以下功能。   (1)将名称为class1.cs的文件添加到该应用程序中。   (2)创建一个名称为MyException的类(继承于Exception),并使用该类处理自定义的异常。 第3篇 C#面向对象编程篇    第13章 异常处理    ·174·       ·179·