第 20 章 异常和状态管理

第 20 章 异常和状态管理

本章内容

20.2 异常处理机制

以下 C# 代码展示了异常处理机制的标准用法,可通过它对异常处理代码块及其用途产生初步认识。代码后面的各小节将正式描述 trycatchfinally 块及其用途,并提供关于它们的一些注意事项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void SomeMethod() {

try {
// 需要得体地进行恢复和/或清理的代码放在这里
}
catch (InvalidOperationException) {
// 从 InvalidOperationException 恢复的代码放在这里
}
catch (IOException) {
// 从 IOException 恢复的代码放在这里
}
catch {
// 从除了上述异常之外的其他所有异常恢复的代码放在这里
...
// 如果什么异常都捕捉,通常要重新抛出异常。本章稍后将详细解释
throw
}
finally {
// 这里的代码对始于 try 块的任何操作进行清理
// 这里的代码总是执行,不管是不是抛出了异常
}
// 如果 try 块没有抛出异常,或者某个 catch 块捕捉到异常,但没有抛出或
// 重新抛出异常,就执行下面的代码
...
}

20.2.1 try

20.2.2 catch

catch 关键字后的圆括号中的表达式称为捕捉类型。C# 要求捕捉类型必须是System.Exception或者它的派生类型。

CLR 自上而下搜索匹配的 catch 块,所以应该将具体的异常放在顶部。

当 CLR 找到匹配的 catch 块时,会执行内层所有 finally 块中的代码。所有内层 finally 块执行完毕后,匹配异常的 catch 块中的代码才开始执行。

catch 块的末尾,我们有以下三个选择。

  • 重新抛出相同的异常,向调用栈高一层的代码通知该异常的发生。

  • 抛出一个不同的异常,向调用栈高一层的代码提供更丰富的异常信息。

  • 让线程从 catch 块的底部退出

20.2.3 finally

finally块包含的是保证会执行的代码。一般在 finally 块中执行try块的行动所要求的资源清理操作。

try 块并不一定要关联 finally 块。 try 块的代码有时并不需要任何清理工作。但是,只要有 finally 块,它就必须出现在所有 catch 块之后,而且一个 try 块最多只能够关联一个 finally 块。

C# 编译器只允许代码抛出从 Exception 派生的对象,但是,CLR 实际允许抛出任何类型的实例。

20.3 System.Exception

20.4 FCL 定义的异常类

Microsoft 本来是打算将 System.Exception 类型作为所有异常的基类型,而另外两个类型 System.SystemExceptionSystem.ApplicationException 是唯一直接从 Exception 派生的类型。另外,CLR 抛出的所有异常都从 SystemException派生,应用程序抛出的所有异常都从 ApplicationException 派生。这样就可以写一个 catch 块来捕捉 CLR 抛出的所有异常或者应用程序抛出的所有异常。

但是,规则没有得到严格遵守。有的异常类型直接从 Exception 派生(IsolatedStorageException);CLR 抛出的一些异常从 ApplicationException 派生 (TargetInvocationException);而应用程序抛出的一些异常从 SystemException 派生(FormatException)。这根本就是一团糟。结果是 SystemException 类型和 ApplicationException 类型根本没什么特殊含义。Microsoft 本该及时将它们从异常类的层次结果中移除,但现在已经不能那样做了,因为会破坏现有的代码对这两个类型的引用。

20.5 抛出异常

强烈建议定义浅而宽的异常类型层次结构,以创建尽量少的基类。原因是基类的主要作用就是将大量错误当作一个错误,而这通常是危险的。基于同样的考虑,永远都不要抛出一个System.Exception 对象,抛出其他任何基类异常类型时也要特别谨慎。

20.6 定义自己的异常类

20.7 用可靠性换取开发效率

20.8 设计规范和最佳实践