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