<4D F736F F D20B5DA3130D5C220B4A6C0EDD2ECB3A3BACDB4EDCEF32E646F63>

Similar documents
untitled

untitled

OOP with Java 通知 Project 4: 4 月 18 日晚 9 点 关于抄袭 没有分数

内容介绍 6.1 任务预览 6.2 异常 6.3 异常种类与层次结构 6.4 异常处理代码块 try-catch-finally 6.5 throw 语句与 throws 子句 6.6 自定义异常类 6.7 异常处理代码块嵌套 6.8 错误与断言 6.9 本章小结 6.10 实训 6: 除法运算程序

untitled

SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 "odps-sdk" 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基

Java程序设计

OOP with Java 通知 Project 4: 4 月 19 日晚 9 点

untitled

《大话设计模式》第一章

Guava学习之Resources

chp6.ppt

无类继承.key

OOP with Java 通知 Project 4: 5 月 2 日晚 9 点

OOP with Java 通知 Project 3: 3 月 29 日晚 9 点 4 月 1 日上课

1: public class MyOutputStream implements AutoCloseable { 3: public void close() throws IOException { 4: throw new IOException(); 5: } 6:

JavaIO.PDF

Microsoft Word - 新1-12.doc

帝国CMS下在PHP文件中调用数据库类执行SQL语句实例

untitled

1 1 大概思路 Nginx 集群之 WCF 大文件上传及下载 BasicHttpBinding 相关配置解析 transfermode messageencoding maxreceivedmessagesize receivetimeout sendtimeout 编写 WCF 服务 客户端程序

2 WF 1 T I P WF WF WF WF WF WF WF WF 2.1 WF WF WF WF WF WF

1 1 大概思路 创建 WebAPI 创建 CrossMainController 并编写 Nuget 安装 microsoft.aspnet.webapi.cors 跨域设置路由 编写 Jquery EasyUI 界面 运行效果 2 创建 WebAPI 创建 WebAPI, 新建 -> 项目 ->

Microsoft Word - 封面

Microsoft Office SharePoint Server MOSS Web SharePoint Web SharePoint 22 Web SharePoint Web Web SharePoint Web Web f Lists.asmx Web Web CAML f

<ADB6ADB1C25EA8FAA6DB2D4D56432E706466>

EJB-Programming-4-cn.doc

JAVA 单元 2.1 四则运算机 ( 一 ) 单元教学进度设计 教学环节 教学内容 教师学生活动活动 反馈 反馈课前作业完成情况 反馈加分 1. 下面哪些是合法的变量名? ( ) A.2variable 答案 :DEG B..variable2 解答 : C.._whatavariable A:/

Java java.lang.math Java Java.util.Random : ArithmeticException int zero = 0; try { int i= 72 / zero ; }catch (ArithmeticException e ) { // } 0,

Microsoft Word - ch04三校.doc

Microsoft Word - 01.DOC

1 Framework.NET Framework Microsoft Windows.NET Framework.NET Framework NOTE.NET NET Framework.NET Framework 2.0 ( 3 ).NET Framework 2.0.NET F

Microsoft PowerPoint - string_kruse [兼容模式]

OOP with Java 通知 : Project 2 提交时间 : 3 月 14 日晚 9 点 另一名助教 : 王桢

《计算概论》课程 第十九讲 C 程序设计语言应用

OOP with Java 通知 : Project 2 提交时间 : 3 月 15 日晚 9 点

OOP with Java 通知 Project 4: 推迟至 4 月 25 日晚 9 点

res/layout 目录下的 main.xml 源码 : <?xml version="1.0" encoding="utf 8"?> <TabHost android:layout_height="fill_parent" xml

0 本章掌握内容 C# 是一门语言 Framework 是运行库和类库 Visual Studio.NET 是开发环境 / 工具 VS2010 的使用 ( 程序 debug) C# 应用程序一般结构 控制台应用程序 窗体应用程序

OOP with Java 通知 Project 3 提交时间 3 月 29 日晚 9 点 Piazza Project 2 投票

while ((ch = fr.read())!= -1) { System.out.print((char) ch); fr.close(); 例 3: 用 BufferedReader 读 TXT 文件 public class FileReaderDemo3 { public static v

拦截器(Interceptor)的学习

目录 1 文件操作之一 版权声明 内容详情 追加文件 拷贝文件 删除文件 移动文件 创建目录 文件夹内容拷贝 文件夹内容删除..

<4D F736F F D C3E6CFF2B6D4CFF35FB5DACEE5D5C D6D0B5C4D2ECB3A3B4A6C0ED5F2E646F63>

第3章.doc


新版 明解C++入門編

untitled

威 福 髮 藝 店 桃 園 市 蘆 竹 區 中 山 里 福 祿 一 街 48 號 地 下 一 樓 50,000 獨 資 李 依 純 105/04/06 府 經 登 字 第 號 宏 品 餐 飲 桃 園 市 桃 園 區 信 光 里 民

OOP with Java 通知 Project 4: 4 月 19 日晚 9 点

OOP with Java 通知 Project 4: 5 月 2 日晚 9 点

《C语言程序设计》教材习题参考答案

1.JasperReport ireport JasperReport ireport JDK JDK JDK JDK ant ant...6

ASP.NET实现下拉框二级联动组件

Microsoft PowerPoint - 4. 数组和字符串Arrays and Strings.ppt [兼容模式]

第一章 章标题-F2 上空24,下空24

EJB-Programming-3.PDF

<4D F736F F D E4345C6BDCCA84323B1E0B3CCD2AAB5E3D6AED2BB2E646F63>

软件工程文档编制

Microsoft PowerPoint - 5. 指针Pointers.ppt [兼容模式]

<4D F736F F D20C8EDC9E82DCFC2CEE7CCE22D3039C9CF>

内 容 简 介 本 书 是 一 本 关 于 语 言 程 序 设 计 的 教 材, 涵 盖 了 语 言 的 基 本 语 法 和 编 程 技 术, 其 中 包 含 了 作 者 对 语 言 多 年 开 发 经 验 的 总 结, 目 的 是 让 初 学 的 读 者 感 受 到 语 言 的 魅 力, 并 掌

PowerPoint 簡報

ex

<4D F736F F D20AC4FBDBDA4FBB67DA96CAABA2DA743A67EAFC5AAA95FA7B9BD5A5F2E646F63>

C H A P T E R 7 Windows Vista Windows Vista Windows Vista FAT16 FAT32 NTFS NTFS New Technology File System NTFS

Microsoft PowerPoint - CPP-Ch Print.ppt [兼容模式]

Microsoft PowerPoint - 11 异常处理 Exception Handling.pptx

CC213

使用MapReduce读取XML文件

NOWOER.OM m/n m/=n m/n m%=n m%n m%=n m%n m/=n 4. enum string x1, x2, x3=10, x4, x5, x; 函数外部问 x 等于什么? 随机值 5. unsigned char *p1; unsigned long *p

KillTest 质量更高 服务更好 学习资料 半年免费更新服务

<4D F736F F F696E74202D BDE1B9B9BBAFB3CCD0F2C9E8BCC D20D1ADBBB7>

untitled

三种方法实现Hadoop(MapReduce)全局排序(1)

FPGAs in Next Generation Wireless Networks WPChinese

使 用 Java 语 言 模 拟 保 险 箱 容 量 门 板 厚 度 箱 体 厚 度 属 性 锁 具 类 型 开 保 险 箱 关 保 险 箱 动 作 存 取 款

Java Access 5-1 Server Client Client Server Server Client 5-2 DataInputStream Class java.io.datainptstream (extends) FilterInputStream InputStream Obj

untitled

FY.DOC


3.1 num = 3 ch = 'C' 2

OOP with Java 通知 Project 2 提交时间 : 3 月 14 日晚 9 点 另一名助教 : 王桢 学习使用文本编辑器 学习使用 cmd: Power shell 阅读参考资料

Microsoft Word - Broker.doc

OOP with Java 通知 Project 4: 5 月 2 日晚 9 点

本章学习目标 小风 Java 实战系列教程 SpringMVC 简介 SpringMVC 的入门案例 SpringMVC 流程分析 配置注解映射器和适配器 注解的使用 使用不同方式的跳转页面 1. SpringMVC 简介 Spring web mvc

}; "P2VTKNvTAnYNwBrqXbgxRSFQs6FTEhNJ", " " string imagedata; if(0!= read_image("a.jpg",imagedata)) { return -1; } string rsp; ytopen_sdk m_sd

Microsoft Word - 第3章.doc


<4D F736F F D20B5DAC8FDCBC4D5C2D7F7D2B5B4F0B0B82E646F63>

(TestFailure) JUnit Framework AssertionFailedError JUnit Composite TestSuite Test TestSuite run() run() JUnit

Microsoft Word - 实验3.doc

Microsoft PowerPoint pm--跟我一起学Visual Studio 2005(1):C#语法篇(上)

概述

全国计算机技术与软件专业技术资格(水平)考试

Microsoft Word - 11.doc

chap07.key

Microsoft PowerPoint - 8. 运算符重载 Operator Overloading.pptx

1.5招募说明书(草案)

Transcription:

142 第 7 章处理异常和错误 异常是指应用程序运行时遇到的错误或程序意外的行为 如, 在运算中除以 0 的操作, 调用代码或程序代码中有错误, 操作系统资源不可用等等 异常处理则是在应用程序发生异常情况时, 采取什么样的操作, 是继续程序的执行, 还是中断用户的操作 为了捕捉和处理异常,C# 提供了 3 个关键字 : catch 和 finally 后面紧跟需要执行的代码块, 这称为一个 块 catch 捕捉 代码块中可能发生的异常, 并调用合适的异常处理器进行异常的处理, 称之为 catch 块,finally 是不管异常是否被处理都必须执行的代码块, 称为 finally 块 一个典型的异常处理结构为 : // 块 // 在这一区块中放置需要被执行的程序代码 catch(exception ex) //catch 块 //exception 表示异常处理器, 可以为系统缺省的异常处理器, 也可以是用户自定义的异常处理器 catch(customexception ex) //catch 块 //exception 表示异常处理器, 可以为系统缺省的异常处理器, 也可以是用户自定义的异常处理器 finally //finally 块 // 它在每次块退出时都执行, 不论退出是由正常控制流引起的还是由未处理的异常引起的 7.1 /catch 块 在开发应用程序时, 可以假定任何代码块都可能引发异常, 特别是 CLR 运行库本身可能引发异常 比如 OutOfMemoryException StackOverflowExcpetion 异常 在一些情况下, 可能会使用 if 语句检测可能会引发异常的条件 比如一个除法运算, 在执行前检测被除数是否为一个不是 0 的数值, 如果是则提示用户除法运算失败 如下面所示的示例代码 static void Main(string[] args) int i=division(100, 3); if (i > 0) Console.WriteLine(" 除法运算成功, 结果为 0", i); 142

143 else Console.WriteLine(" 除法运算失败!"); Console.ReadLine(); static int division(int x, int y) if (y == 0) return -1; else decimal d = x / y; return (int)d; 在上面的代码中, 程序使用 if 语句对被除数进行了检查 这看起来似乎工作得很好, 但是如果需要对除数与被除数进行更多的控制, 就不得不在代码中使用大理的 if 语句进行判断 而且, 难免会有疏漏 并且, 应用程序错误必须在运行时才能发现, 这对于应用程序逻辑错误将会难以调试和处理 使用 /catch 块, 能够有效的避免 if 语句块的不足, 通过将可能会产生异常的代码放置于 块中, 在 catch 块中追踪可能会产生的异常, 还可以在 catch 块中捕捉应用程序的全局异常 并且.NET 对于异常处理提供了编译时支持, 因此使用 /catch 块是比较好的选择 如果使用异常处理方式, 如下面的示例所示 static void Main(string[] args) int i=division(100,0); Console.ReadLine(); static int division(int x, int y) return (int)x / y; catch (DivideByZeroException ex) throw new Exception(" 除法运算发生了一个错误!"+Environment.NewLine+ex.Message); catch (Exception ex) throw new Exception(" 除法运算发生了一个错误!"); 上面的示例代码中, 通过使用 /catch 语句块, 封装了在运行除法运算中可能会引发的错误, 如果在为被除数赋一个 0 值, 在编译时将会编译不过,debug 模式下 Visual studio2005 将会产生如图 7.1 所示 143

144 的错误 如果直接运行程序, 在控制台窗口将会产生如图 7.2 所示的输出 图 7.1 编译时捕捉到异常, 弹出一个 Exception 窗口 图 7.2 运行时捕捉到异常 示例程序中, 通过在 块定义要执行的语句 在 catch 块中捕捉可能出现的异常, 并使用 throw 语句显示抛出了一个异常 在示例程序中, 定义了两个 catch 块 在.NET 中, 可以定义多个 catch 块, 但只能有一个 块 示例代码的第一个 catch 块中, 使用了 DivideByZeroException 异常 这个异常是一个具体异常 他所代表的就是被 0 除的错误, 第二个 catch 块中的异常类通常称为基类异常, 该异常具有比第一个异常更粗的粒度, 在这个示例中是 Exception 类型,Exception 类型是所有异常类的基类 7.2 finally 块 不论是否捕捉到异常,finally 块中的代码一定会执行 举个例子, 在处理文件时, 如果打开了一个文件, 执行一些写入操作, 这时候发生了致命错误, 由于 catch 块捕捉到异常后, 控制权会直接跳转到异常处理结尾, 那么这时候这个打开的文件一直没有被关闭 显然这会造成资源占用, 如果文件是以独占的方式被打开的话, 其他操作将无法顺利进行 另外一个比较常见的例子是数据库操作, 如果正在执行一个行级锁定更新时, 发生了异常, 那么这会导致这个锁一直不能被释放, 继而影响到其余的步骤无法顺利进行 使用 finally 块可以清除 中分配的任何资源, 以及运行任何即使在发生异常时也必须执行的代码 代码控制权最终总是传递给 finally 块, 与 块的退出方式无关 因此,finally 块提供了一种保证资源清理或者是资源恢复的机制 下面的示例程序代码演示了在执行一个文件读写操作的时候, 不论是否发生异常, 最终都关闭文件流 关于文件处理的章节读者可参考第 11 章 static void Main(string[] args) const string filepath = @"C:\FinallyDemo.txt"; FileStream fs=null; Console.WriteLine(" 开始执行文件的比较操作 "); fs = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite); byte[] bytes = Encoding.Default.GetBytes(" 这是一个字符串, 将插入到文本文件 "); // 向流中写入指定的字节数组 fs.write(bytes,0,bytes.length); // 将缓冲区的内容存储到媒介并清除缓冲区 144

145 fs.flush(); // 将流指针移到开头 fs.seek(0, SeekOrigin.Begin); byte[] bytes2 = new byte[bytes.length]; fs.read(bytes2, 0, bytes.length); string str = Encoding.Default.GetString(bytes2); Console.WriteLine(" 从文件中读出的字符串为 " + Environment.NewLine+str); catch (IOException ex) Console.WriteLine(" 发生了文件处理的错误!" + ex.message); finally Console.WriteLine(" 不论是否发生异常, 都会执行 finally 到这里 "); if (fs!= null) fs.close(); Console.ReadLine(); 程序没有产生异常的情况下, 输出结果如图 7.3 所示 如果程序发生了异常, 在这里笔者将 FileMode.OpenOrCreate 改为 FileMode.CreateNew, 因为文件己经存在, 将会产生一个 IOException 异常, 如图 7.4 所示 图 7.3 异常输出的结果 图 7.4 异常输出窗口 程序输出窗口如图 7.5 所示 图 7.5 产生异常后的程序输出窗口 从输出窗口可以看到, 不论是否发生了异常, 程序都执行了 finally 块中的代码 7.3 预定义异常的类 在讨论 catch 语句块时, 提到过两个异常类 :DivideByZeroException 和 Exception 类 System.Exception 是 DivideByZeroException 的基类, 也是所有其他异常类的基类 在 System.Exception 基类之后, 又分了两种类型的异常 : 从 SystemException 派生的预定义 CLR 异常类 145

146 从 ApplicationException 派生的用户定义的应用程序异常类 如果要定义自己的异常类或者建立自定义的异常体系, 需要从 ApplcationException 类中派生 SystemException 类中的异常是 CLR 预定义的异常类 表 7.1 对 SystemException 中比较常用类进行了介绍, 更多的异常类信息可以参考.NET 中的文档 异常类 System.ArgumentException System.ArgumentNullException System.ArgumentOutOfRangeException System.AccessViolationException System.ArithmeticException System.DivideByZeroException System.NotFiniteNumberException System.OverflowException System.Collections.Generic.KeyNotFoundException System.Data.DataException System.Data.ConstraintException System.Data.InvalidConstraintException System.Data.MissingPrimaryKeyException System.Data.ReadOnlyException System.IO.IOException System.IO.DirectoryNotFoundException System.IO.DriveNotFoundException System.IO.EndOfStreamException System.IO.FileLoadException System.IO.FileNotFoundException System.IO.PathTooLongException 表 7.1.NET 预定义的异常类 描述表示当方法中的参数无效时引发该异常 ArgumentNullException: 表示向不接受 null 的参数传递一个 null 值时, 引发异常 ArgumentOutOfRangeException: 表示向参数传递超出指定的范围的值时, 将引发该异常 如果试图访问或者是读写受保护内存时引发的异常 在运算 类型转换或转换操作中的错误而引发的异常 DivideByZeroException: 被零除时引发异常 NotFiniteNumberException: 当浮点数为正无穷大 负无穷大或非数字 (NaN) 时引发的异常 OverflowException: 在算术运算 类型转换或者是转换操作导致溢出时引发的异常如果使用集合中不存在的键来从该集合检索元素时表示使用 ADO.NET 组件发生错误时引发的异常 ConstraintException: 表示在尝试执行违反约束的操作时引发的异常 InvalidConstraintException: 表示在不正确地尝试创建或访问关系时引发的异常 MissingPrimaryKeyException: 表示在尝试访问没有主键的表中的行时引发的异常 ReadOnlyException: 表示在尝试更改只读列的值时引发的异常 发生 I/O 错误时引发的异常 DirectoryNotFoundException: 表示找不到文件或目录时所引发的异常 DriveNotFoundException: 表示访问的驱动器或共享不可用时引发的异常 EndOfStreamException: 表示读操作试图超出流的末尾时引发的异常 FileLoadException: 表示找到托管程序集却不能加载它时引发 FileNotFoundException: 表示访问磁盘上不存在的文件失败时引发 System.Exception 具有一些属性, 提供了关于引发的异常时产生的一些信息, 例如错误代码位置, 异常描述信息等 在捕捉异常时, 可以利用这些信息进行错误的处理 或者为用户提供关于该异常的描述信息 表 7.2 列出了 System.Exception 的属性列表 表 7.2 System.Exception 属性列表 属性 类型 描述 Message String 描述错误的可读文本 当异常发生时, 运行库产生文本消息通知用户错误的性质并提供解决该问题的操作建议 Data IDictionary 使用由 Data 属性返回的 System.Collections.IDictionary 对象来存储和检索与异常相关的补充信息 Source String 产生异常的程序集的名称 StackTrace String 发生异常时调用堆栈的状态 StackTrace 属性包含可以用来确定代码中错误发生位置的堆栈跟踪, 对于调试来说, 这是非常有用的信息 TargetSite MethodBase 抛出异常的方法 HelpLink String 获取或设置异常的关联帮助文件的链接 InnerExceptoin Exception 创建对以前的异常进行捕捉的新异常 处理第二个异常的代 146

147 码可利用前一个异常的其他信息更适当地处理错误 如果不存在前一个异常, 则为 null 下面举一个使用这些属性的例子, 图 7.6 显示了这个例子的数据流程图 在这个示例中,RunMethod 调用 Method2,Method2 调用 Method1 Method1 抛出一个异常,Method2 捕获异常并将 Method1 中的异常传递给 RunMethod 方法,RunMethod 调用 Exception 类的属性在用户界面显示异常信息 示例代码如下所示 图 7.6 样例代码数据流程图 using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Collections; namespace ExceptionInfos class Program static void Main(string[] args) Console.WriteLine(" 显示额外的信息的结果为 :"); Console.WriteLine(); RunMethod(true); Console.WriteLine(" 不显示额外的信息的结果为 :"); Console.WriteLine(); RunMethod(false); Console.ReadLine(); // 显示当前异常和前一个异常的 Exception 属性信息 static void RunMethod(bool displaydetails) // 调用 Method2 方法 147

148 Method2(); // 捕捉全局异常 catch (Exception ex) Console.WriteLine(" 当前的异常 Message 属性值为 :0", ex.message); Console.WriteLine(" 内部异常的 Message 属性值为 :0", ex.innerexception.message); Console.WriteLine(" 当前异常的 Source 属性值为 :0", ex.source); Console.WriteLine(" 内部异常的 Source 属性值为 :0", ex.innerexception.source); Console.WriteLine(" 当前异常 StackTrace 属性值为 :0", ex.stacktrace); Console.WriteLine(" 内部异常的 StackTrace 属性值为 :0", ex.innerexception.stacktrace); Console.WriteLine(" 当前异常 TargetSite 属性值为 :0", ex.targetsite); Console.WriteLine(" 内部异常的 TargetSite 属性值为 :0", ex.innerexception.targetsite); Console.WriteLine(" 当前异常 HelpLink 属性值为 :0", ex.helplink); Console.WriteLine(" 内部异常的 HelpLink 属性值为 :0", ex.innerexception.helplink); // 是否显示额外的异常信息 if (displaydetails) // 如果内部异常的 Data 属性不为 null if (ex.innerexception.data!= null) Console.WriteLine(" 额外的异常信息是 :"); foreach (DictionaryEn de in ex.innerexception.data) Console.WriteLine(" 键是 :0, 值为 1", de.key, de.value); // 为 Exception 对象设置值, 并直接抛出异常 static void Method1() Exception ex = new Exception(" 这是原始异常消息 "); string s = " 来自 Method1 的信息 "; int i = 100; DateTime dt = DateTime.Now; ex.data.add(" 方法信息 ", s); ex.data[" 整数值 "] = i; ex.data[" 时间值 "] = dt; throw ex; // 为内部异常的 Data 属性添加更多的信息 static void Method2() // 调用 Method1, 直接引发异常 Method1(); 148

149 catch (Exception ex) Exception e = new Exception(" 这是来自 Method2 的异常信息 ", ex); e.innerexception.data[" 附件信息 "] = " 来自 Method2 的信息 "; e.innerexception.data.add(" 更多信息 ", " 来自 Method2 的更多信息 "); throw e; 显示 Data 属性值的输出结果如图 7.7 所示 不显示 Data 属性值的输出结果如图 7.8 所示 图 7.7 显示 Exception 属性的输出结果 图 7.8 显示 Exception 属性的输出结果 7.4 处理异常 本章前面的小节中, 己经详细介绍了 C# 提供的异常处理关键字, 本节将讨论如何在.NET 处理异常 7.4.1 处理多个异常 如果 块中的语句有可能引发多种类型的异常, 那么需要在 catch 块捕捉这些异常并进行相应的处理 在 10.1 节, 讨论 /catch 块时, 曾经介绍过在 块中可以包含多个 catch 块 再以除法运算作为例子, 例如下面的示例代码 static void MorecatchMethod(int x,int y) x / y; catch (DivideByZeroException ex) // 异常处理代码 catch (ArgumentNullException ex) // 异常处理代码 149

150 catch (Exception ex) // 异常处理代码 块中包含了可能产生异常的代码, 当异常引发时, 第一个 catch 块捕捉 块中产生的异常, 如果异常类型与 catch 块中的异常相匹配, 在本例中则指如果异常类型为 DivideByZeroException 那么执行权跳转到第一个 catch 块中的代码, 执行完毕跳出异常处理程序块 如果异常类型不匹配, 则跳转到下一个 catch 块, 依序下去, 直到跳转到最后一个 catch 块 如果所有的异常都不匹配, 则交由 CLR 接管异常处理 这种层次式匹配异常的方式称为异常筛选 在设计多个异常处理时, 应将具体异常类放在前面, 而将基础异常类放在最后面 在示例代码中, 最后一个 catch 块的类型为 Exception, 本章己经讨论过, 这个类是所有异常类的基类 因此, 块中的代码如果不匹配任何具体异常, 那么最终将会被 Exception 捕获 C# 编译器对异常筛选提供了支持, 如果非要将一个基础异常类放在具体异常类的前面, 编译器将弹出警告信息 如果将上一个示例中的最后的 catch 块与前一个 catch 块调换位置, 编译时将会产生如图 7.9 所示的异常 图 7.9 异常处理错误 7.4.2 处理和传递异常 System.Exception 类定义了几个在处理异常时相当有用的属性, 在 10.3 节讨论预定义异常类时己经详细的介绍过 Exception 类有一个属性 :InnerException, 这是一个 Exception 类型的属性 表示当前所抛出异常中所内含的另一个异常, 在 C# 的异常处理中使用这个属性进行可以进行异常传递 假如有一个方法 A, 抛出一个异常 A1 有一个方法 B, 调用方法 A, 并且也抛出一个异常 B1 如果希望在抛出 B1 的同时, 也传递方法 A 抛出的异常 A1, 那么需要将 A1 作为一个异常参数传递给 B1, 这样当抛出 B1 时, 也抛出了 A1 下面的代码演示了异常传递的过程 static void Main(string[] args) A2(); catch (Exception ex) Console.WriteLine(" 捕获的异常消息 :0", ex.message); Console.WriteLine(" 捕获的异常所传递的异常的消息 :0", ex.innerexception.message); Console.ReadLine(); 150

151 //A1 直接抛出一个异常 static void A1() Exception ex = new Exception(" 这是来自 A1 的异常消息 "); throw ex; //A2 调用 A1, 并将 A1 抛出的异常保存到 InnerException 中 static void A2() A1(); catch(exception e) Exception ex = new Exception(" 这是来自 A2 的异常消息 ",e); throw ex; 图 7.10 为此段代码的输出结果 图 7.10 传递的异常示例结果从输出窗口可以看到, 通过 InnerException 属性, 程序获取了来自内部异常的信息 如果内部异常没有被引发, 则 InnerException 属性值为 null 因此在使用这个属性前, 通常需要判断其值是否为 null 值 7.4.3 从异常中恢复 前面章节中曾经讨论过, 无论异常是否引发,finally 块中的代码都一定会执行 因此, 可以在这个代码块中进行一些清理资源和恢复的工作 比如应用程序状态恢复, 将打开的文件关闭, 关闭数据库连接等操作 在 catch 语句块中, 如果能够确知 块中的代码将会引发哪种类型的异常, 可以在 catch 块中进执行异常恢复的代码, 如果不能确知哪种类型的异常将会被触发, 可以使用不带任何表达式的 catch 语句 在异常恢复的代码执行完成后, 再显示抛出异常, 让调用方能够捕获到该异常并进行相应的处理操作, 这种类型的操作也可以称为异常回滚 举例来说, 如果正在对两个文件同时执行写操作, 如果对第一个文件的写操作己经完成, 执行到对第二个文件的写操作时, 抛出了异常 假设程序要求两个文件必须全部写入成功, 只要有一个文件的写入失败, 就该回滚对第一个文件的写入, 这好比是一个数据库的事务 下面的示例代码演示了如何从异常中恢复 using System; using System.Collections.Generic; 151

152 using System.Text; using System.IO; namespace RollbackException class Program static void Main(string[] args) // 在 块中调用写入文件的代码 WriteFile(); catch (Exception ex) Console.WriteLine(ex.Message); Console.ReadLine(); /// <summary> /// 异常恢复的示例, 假定有两个文件, 只要有一个写入失败, 就全部回滚 /// </summary> static void WriteFile() // 文件的路径 const string filepath1=@"c:\roolbackexception1.txt"; const string filepath2=@"c:\roolbackexception2.txt"; FileStream fs = null; FileStream fs1 = null; long fsposition = 0; long fs1position = 0; string str = " 这是需要被写入到两个文件中的字符串 "; // 将 string 类型转换为 byte 数组 byte[] bytes = Encoding.Default.GetBytes(str.ToCharArray()); fs = new FileStream(filePath1, FileMode.OpenOrCreate, FileAccess.ReadWrite); // 先将流位置移到文件尾部 fs.seek(0, SeekOrigin.End); // 获取 Position, 以用于恢复 fsposition = fs.position; // 写入指定的字节数组 fs.write(bytes, 0, bytes.length); // 写入磁盘清除缓冲区 fs.flush(); fs1 = new FileStream(filePath2, FileMode.CreateNew, FileAccess.ReadWrite); fs1.seek(0, SeekOrigin.End); fs1position = fs1.position; fs1.write(bytes, 0, bytes.length); fs1.flush(); 152

153 //catch 后面并没有加任何表达式, 这是可行的, 这表示获取所有与 CLS 兼容或不兼容的异常 // 一旦发生错误, 则将文件恢复到写入前的状态 catch if (fs!= null) fs.position = fsposition; fs.setlength(fsposition); if (fs1!= null) fs1.position = fs1position; fs1.setlength(fs1position); throw; // 执行文件关闭操作 不管有没有引发异常 finally if (fs!= null) fs.close(); if (fs1!= null) fs1.close(); 示例代码中, 使用两个 FileStream 类操作文件, 在代码的 catch 块中, 并没有添加任何特定的异常类, 这表示将捕获所有的异常, 包括与 CLS( 公共语言规范 ) 兼容的异常 ( 继承自 Exception 类 ) 或不兼容的异常 只要异常产生,catch 块中的代码会将文件恢复到执行操作以前的状态, 最后抛出异常给调用代码 7.5 设计自己的异常 如果系统提供的异常类, 己经不能够满足应用系统开发的需要, 或者开发团队需要一套自定义异常处理机制, 可以创建自定义的异常类 为创建自定义异常类, 应该直接或间接的继承自 ApplicationException 类 自定义异常类应该有良好的命名, 一般建议的名称是 : 错误的描述性名称 +Exception 自定义异常类应该定义 3 个构造函数 : 默认构造函数, 接收错误消息的构造函数, 接收错误消息和内部异常对象的构造函数 上一小节在介绍异常恢复时, 举了一个两个文件同步的例子 本节将创建一个自定义的异常类, 当上节示例中产生任何文件操异常时, 不是简单的将系统异常抛出, 而是抛出一个自定义的 153

154 WriteFailedException 类型的异常 下面是 WriteFailedException 异常的程序代码 using System; using System.Collections.Generic; using System.Text; namespace CustomException /// <summary> /// 自定义异常类的示例 /// </summary> public class WriteFailedException:ApplicationException // 直接调用基类构造函数 public WriteFailedException() :base(" 对两个文件的操作产生了一个异常 ") // 直接调用基类构造函数 public WriteFailedException(string Message) : base(message) // 直接调用基类构造函数 public WriteFailedException(string Message, Exception inner) : base(message, inner) 定义好异常类后, 就可以像系统内置的异常类一样进行调用了 下面是调用示例代码 static void Main(string[] args) WriteFile(); // 捕捉自定义的异常代码 catch (WriteFailedException ex) Console.WriteLine(ex.Message); Console.ReadLine(); static void WriteFile() throw new WriteFailedException(); 调用代码只是简单的抛出一个自定义异常, 并显示异常信息到控制台窗口 输出结果如图 7.11 所示 154

155 图 7.11 自定义异常示例自定义异常中, 除了简单的实现这几个构造函数外, 还可以添加自己的字段和属性 如果异常需要跨越应用程序进行操作, 还可以定义可序列化的异常, 如果读者对这些更进阶的异常处理感兴趣, 可以查阅.NET 文档或者 MSDN 7.6 小结.NET 提供了相当强大的异常处理机制, 要完全理解与掌握, 需要读者多实践多思考 本章中, 首先通过对 /catch/finally 块的介绍, 了解了异常处理机制与传统错误检测机制的比较 然后介绍了预定义的异常类, 并重点讨论了 Exception 类的属性, 这些属性在异常处理方面是相当重要的 接下来, 通过处理多个异常 处理和传递异常 从异常中恢复, 这 3 个比较常用的异常处理方面, 进行了实例探讨, 最后, 本节实现了一个自定义的异常 155