文章目录
-
- [23.1 什么是异常](#23.1 什么是异常)
- [23.2 try 语句](#23.2 try 语句)
- [23.3 异常类](#23.3 异常类)
- [23.4 catch 子句](#23.4 catch 子句)
- [23.5 异常过滤器](#23.5 异常过滤器)
- [23.6 catch 子句段](#23.6 catch 子句段)
- [23.7 finally 块](#23.7 finally 块)
- [23.8 为异常寻找处理程序](#23.8 为异常寻找处理程序)
- [23.9 进一步搜索](#23.9 进一步搜索)
-
- [23.9.1 一般法则](#23.9.1 一般法则)
- [23.9.2 搜索调用栈的示例(*)](#23.9.2 搜索调用栈的示例(*))
- [23.10 抛出异常](#23.10 抛出异常)
- [23.11 不带异常对象的抛出](#23.11 不带异常对象的抛出)
- [23.12 throw 表达式](#23.12 throw 表达式)
23.1 什么是异常
异常是程序中的运行时错误,它违反了系统约束或应用程序约束,或是正常操作时不会发生的状况。如果程序没有提供处理异常的代码,系统会挂起这个程序。例如,下面的代码在试图用 0 除一个数时抛出一个异常:
在没有异常处理程序的情况下,应用程序将停止(或者崩溃),并向用户显示非常不友好的错误消息。异常处理的目标是通过以下操作来响应异常:
- 在有限的几种情况下采取纠正措施,让应用程序继续运行。
- 记录有关异常的信息,以便开发团队可以解决该问题。
- 清理任何外部资源,例如可能保持打开的数据库连接。
- 向用户显示友好的信息。
23.2 try 语句
try 语句用来指明为避免出现异常而被保护的代码段,并在发生异常时提供代码处理。其包含 3 个部分组成:
- try 块。
- catch 子句。
- finally 块。
图23.1 try 语句的结构
处理异常
将上述代码段放在一个 try 块中,并提供一个简单的 catch 子句来捕获并处理异常。
23.3 异常类
BCL 定义了许多异常类,每一个类代表一种指定的异常类型。当一个异常发生时,CLR 创建该类型的异常对象并寻找适当的 catch 子句以处理它。
所有异常类都派生自 System.Exception 类,System.Exception 类派生自 System.Object 类。
图23.2 异常层次的结构
异常对象含有只读属性,该属性提供有助于调试应用程序的异常信息。
表23.1 异常对象的一部分属性
23.4 catch 子句
catch 子句处理异常,有如下 4 种形式:
图23.3 catch 子句的 4 种形式
- 一般 catch 子句(形式 1):能接受任何异常,但不确定引发异常的异常类型,只能进行普通处理和清理。
- 特定 catch 子句(形式 2):把一个异常类的名称作为参数,匹配指定类或派生自它的异常类的异常。
- 带对象的特定 catch 子句(形式 3、4):提供的异常信息最多,可以在 catch 子句块内部访问异常变量的属性,以获取异常的详细信息。
23.5 异常过滤器
形式 4 的 catch 子句是在 C# 6.0 中添加的,相较于形式 3,异常对象还需满足特定条件,该条件被称为过滤器。这允许程序员编写更小、更专一的异常处理程序,而无需再单个处理程序中包含大量 if 语句。
有关 when 子句的重要特征如下:
- 必须包含谓词表达式(返回值为 true 或 false)。
- 不能是异步的。
- 不应使用任何需要长时间运行的操作。
- 谓词表达式中发生的异常会被忽略。
23.6 catch 子句段
catch 子句段可以包含多个 catch 子句。
图23.4 try 语句的 catch 子句段结构
当发生异常时,系统按顺序搜索 catch 子句的列表,第一个匹配该异常对象类型的 catch 子句被执行。
- catch 子句必须以特定顺序排列。最特定的异常类型排第一,最普通的类型排最后。
- 如果有一个一般 catch 子句,则必须放在最后一个。
- 不鼓励使用一般 catch 子句,因为它允许程序继续执行从而隐藏了特定错误,让程序处于一种位置的状态。
23.7 finally 块
如果程序的控制流进入了带 finally 块的 try 语句,那么 finally 始终会被执行。
图23.5 finally 块的执行
即是 try 块中有 return 语句,或在 catch 块中抛出一个异常,finally 块也总是会在返回到调用代码之前执行。
上述代码在 inVal 值为 5 时也会打印 finally 块中的语句。
23.8 为异常寻找处理程序
当程序抛出异常时,系统查看该程序是否提供了异常处理程序,具体流程如下:
- 如果在 try 块内发生了异常,系统会查看是否有任何一个 catch 子句能处理该异常。
- 如果找到了适当的 catch 子句:
- 该 catch 子句被执行。
- 如果有 finally 块,那么它被执行;否则,继续在最后一个 catch 子句之后执行。
图23.6 在当前 try 语句中有处理程序的异常
23.9 进一步搜索
如果异常在一个没有被 try 语句保护的代码段抛出,或者如果 try 语句没有匹配的异常处理程序,系统将不得不进一步寻找匹配的处理程序。即,按顺序搜索调用栈,查看是否存在带匹配的处理程序的封装 try 块。
如果异常发生在 Method2 内的 try 块内部,系统会执行如下操作:
- 首先查看 Method2 是否有能处理该异常的异常处理程序:
- 如果有,Method2 处理,程序继续执行。
- 否则,系统沿着调用栈找到 Method1,搜寻适当的处理程序。
- 如果 Method1 有一个适当的 catch 子句,那么系统将:
- 回到栈顶,即 Method2 处。
- 执行 Method2 的 finally 块,并将 Method2 弹出栈。
- 执行 Method1 的 catch 子句和 finally 块。
- 如果 Method1 没有适当的 catch 子句,系统继续搜索调用栈。
图23.7 搜索调用栈
23.9.1 一般法则
图23.8 处理异常的一般法则
23.9.2 搜索调用栈的示例(*)
23.10 抛出异常
使用 throw 语句使代码显示抛出异常,throw 语句的语法如下:
下面的代码在 try 块中进行参数 null 检查,创建并抛出 ArgumentNullException 异常。该实例在 catch 语句中被捕获,错误被打印出来。
23.11 不带异常对象的抛出
throw 语句可以在 catch 块内部不带异常对象使用。
- 该形式重新抛出当前异常,系统会继续搜索,为该异常寻找另外的处理程序。
- 这种形式只能用在 catch 语句内部。
23.12 throw 表达式
代码中有些地方不允许使用语句,而只能使用表达式。C# 7.0 后,可以在只能应用表达式的地方使用 throw 表达式,其语法和 throw 语句相同。