第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·