7.3 引发异常
当你必须捕获异常时,其他人首先必须首先能够引发异常。而且,不仅其他人能够引发,你也可以负责引发。其相当简单:
throw new ArgumentException("Argument can't be 5");
你所需要的是throw 语句和一个适当的异常类。我已经从表7.1提供的清单中选出一个异常给这个例子。
表 7.1 Runtime提供的标准异常
异常类型 描述
Exception 所有异常对象的基类
SystemException 运行时产生的所有错误的基类
IndexOutOfRangeException 当一个数组的下标超出范围时运行时引发
NullReferenceException 当一个空对象被引用时运行时引发
InvalidOperationException 当对方法的调用对对象的当前状态无效时,由某些方法引发
ArgumentException 所有参数异常的基类
ArgumentNullException 在参数为空(不允许)的情况下,由方法引发
ArgumentOutOfRangeException 当参数不在一个给定范围之内时,由方法引发
InteropException 目标在或发生在CLR外面环境中的异常的基类
ComException 包含COM 类的HRESULT信息的异常
SEHException 封装win32 结构异常处理信息的异常
然而,在catch语句的内部,你已经有了随意处置的异常,就不必创建一个新异常。可能在表7.1 中的异常没有一个符合你特殊的要求——为什么不创建一个新的异常?在即将要学到小节中,都涉及到这两个话题。
7.3.1 重新引发异常
当处于一个catch 语句的内部时,你可能决定引发一个目前正在再度处理的异常,留下进一步的处理给一些外部的try-catch 语句。该方法的例子如 清单7.8所示。
清单 7.8 重新引发一个异常
2: {
3: checked
4: {
5: for (;nCurDig <= nComputeTo; nCurDig++)
6: nFactorial *= nCurDig;
7: }
8: }
9: catch (OverflowException oe)
10: {
11: Console.WriteLine("Computing {0} caused an overflow exception", nComputeTo);
12: throw;
13: }
注意,我不必规定所声明的异常变量。尽管它是可选的,但你也可以这样写:
throw oe;
现在有时还必须留意这个异常。
7.3.2 创建自己的异常类
尽管建议使用预定义的异常类,但对于实际场合,创建自己的异常类可能会方便。创建自己的异常类,允许你的异常类的使用者根据该异常类采取不同的手段。
在清单 7.9 中出现的异常类 MyImportantException遵循两个规则:第一,它用Exception结束类名。第二,它实现了所有三个被推荐的通用结构。你也应该遵守这些规则。
清单 7.9 实现自己的异常类 MyImportantException
2:
3: public class MyImportantException:Exception
4: {
5: public MyImportantException()
6: :base() {}
7:
8: public MyImportantException(string message)
9: :base(message) {}
10:
11: public MyImportantException(string message, Exception inner)
12: :base(message,inner) {}
13: }
14:
15: public class ExceptionTestApp
16: {
17: public static void TestThrow()
18: {
19: throw new MyImportantException("something bad has happened.");
20: }
21:
22: public static void Main()
23: {
24: try
25: {
26: ExceptionTestApp.TestThrow();
27: }
28: catch (Exception e)
29: {
30: Console.WriteLine(e);
31: }
32: }
33: }
正如你所看到的,MyImportantException 异常类不能实现任何特殊的功能,但它完全基于System.Exception类。程序的剩余部分测试新的异常类,给System.Exception 类使用一个catch 语句。
如果没有特殊的实现而只是给MyImportantException定义了三个构造函数,创建它又有什么意义呢?它是一个重要的类型——你可以在catch语句中使用它,代替更为普通的异常类。可能引发你的新异常的客户代码可以按规定的catch代码发挥作用。
当使用自己的名字空间编写一个类库时,也要把异常放到该名字空间。尽管它并没有出现在这个例子中,你还是应该使用适当的属性,为扩展了的错误信息扩充你的异常类。
7.4 异常处理的“要”和“不要
作为最后的忠告之语,这里是对异常引发和处理所要做和不要做的清单:
。当引发异常时,要提供有意义的文本。
。要引发异常仅当条件是真正异常;也就是当一个正常的返回值不满足时。
。如果你的方法或属性被传递一个坏参数,要引发一个ArgumentException异常。
。当调用操作不适合对象的当前状态时,要引发一个 InvalidOperationException异常。
。要引发最适合的异常。
。要使用链接异常,它们允许你跟踪异常树。
。不要为正常或预期的错误使用异常。
。不要为流程的正常控制使用异常。
。不要在方法中引发 NullReferenceException或IndexOutOfRangeException异常。
7.5 小结
这一章由介绍溢出校验开始。你可以使用编译器开关(默认是关),使整个应用程序允许或禁止溢出校验。如果需要微调控制,你可以使用校验和非校验语句,它允许你使用或不使用溢出校验来执行一段代码,尽管没有给应用程序设置开关。
当发生溢出时,一个异常就被引发了。如何处理异常取决于你。我提出了各种途径,包括你最有可能贯穿整个应用程序使用的:try、catch 和finally 语句。在伴随的多个例子中,你学到了它与WIN32结构异常处理(SEH)的差别。
异常处理是给类的用户; 然而,如果你负责创建新的类,就可以引发异常。有多种选择:引发早已捕获的异常,引发存在的框架异常,或者按规定的实际目标创建新的异常类。
最后,你需要阅读引发和处理异常的各种“要”和“不要”。
评论