求知若饥,虚心若愚
多异常类型
- throw表达式,c#7.0新增,原先如果要抛出变量为空异常需要先判空再执行抛出异常语句,现在可结合空合并操作符,a ?? throw new xxxException()
- 参数为空要抛出ArgumentNullException,而不是NullReferenceException,后者一般不需要程序员主动抛出。尽量用?.来避免空异常
- 使用nameof来代替硬编码
- 经可能抛出具体的类型,太宽泛的异常类型对定位问题没太大帮助。
捕捉异常
- 使用Switch语句捕捉,可有多个catch块,c#会根据继承链选择最匹配的,但要注意顺序(具体到常规)
- c#6.0起,catch块还支持增加一个when语句,为true时才执行,但该语句要避免异常,否则会被忽略,导致语句结果都是false
- 使用空throw语句从新抛出异常而不影响原有调用栈信息
常规catch块
- 不管是不是从Syetem.Exception派生的异常都会被包装成从Exception派生,所以catch(Syetem.Exception exception)能捕捉所有异常,而空的catch语句行为和这个一致
规范
-
只捕捉你能处理的异常
通常,一些类型的异常可以处理,但是另一些类型的异常不能处理。例如,试图打开一个正在使用的文件来进行独占式的读/写访问,会引发一个System.IO.IOException,因为文件已经在使用了。通过捕捉这种类型的异常,代码可以向用户报告该文件正在使用,并允许用户选择取消或者重试。只有那些已知对车的异常才应捕捉。其他异常类型应该留在栈中较高的调用者去处理。
-
不要隐藏你不能完全处理的异常
新手程序员常犯的一个错误是捕捉所有的异常,然后假装什么都没有发生,而不向用 户报告未处理的异常。这有可能导致严重的系统问题逃过检测。除非代码采取现实的行 动来处理一个异常,或者显示的确定一个异常无害,否则catch块应当重新引发异常,而 不是在捕捉了之后在调用者面前隐藏他们。尤其要注意的是catch(System.Exception)和 常规catch块应放在调用栈中较高的位置,除非你决定在块中重新引发异常
-
尽可能的少使用System.Exception和常规Catch块
几乎所有的异常都是从System.Exception派生的。然而,处某些System.Exception的方式是不对它们进行处理,或者尽快以正常的方式关闭程序。例如,想System.OutOfMemoryException这样的异常是无法恢复的,所以对策就是关闭应用程序。相应的catch块只应运行清理或紧急代码(比如保存任何易失的数据),然后马上关闭相应的应用程序或者使用throw;语句重新引发异常。
-
避免在调用栈较低的位置报告或记录异常
新手程序员倾向于异常一发生就记录它,或者向用户报告它。然而,由于当前正处在调用栈中较低的位置,而这些位置很少能够完整的处理异常,所以会重新引发异常。像这样的catch块不应该记录异常,也不应该向用户报告。假如异常被巨鹿,然后又被重新引发(调用栈中较高的调用者可能做同样的事),就会造成重复出现的异常记录项。更糟的是,向用户显示异常可能并不匹配应用程序。例如,在一个windows应用程序中,使用System.COnsole.WriteLine(),用户永远看不到显示的内容。类似的,在无人值守的命令行进程中显示一个对话框,可能根本就不会被别人看到,而且可能使应用程序冻结在这个位置。与日志记录和异常有关的用户界面应该保留到调用栈中较高的位置。
-
在一个Catch块中可以使用throw;而不是throw <异常语句>语句
可以在一个catch块中重新引发异常。例如,在catch(ArgumenhtNullException exception)的实现中,可以包含对throw exception的调用。然而重新引发异常,会将栈追踪重置为重新引发的位置,而不是原始引发的位置。所以,假设如果不是要重新引发一个不同类型的异常,或者不是要故意隐藏原始调用栈,就应该使用throw ; 语句,允许相同的异常在调用栈中向上传播。
-
重新引发不同的异常是要小心
在一个catch块的内部,假如你重新引发一个不同的异常,那么不仅会重置引发点,还会隐藏原始异常。为了保留原始异常,需要重新设置新异常的InnerException属性(该属性通常可以通过构造器来赋值)。通常只应在以下情况下才重新引发一个不同的异常
a)更改异常类型可以更好的澄清问题
例如,在对Logon(User user)的一个调用中,假如遇到用户列表文件不能访问的情况,那么重新引发一个不同的异常类型,要比引发System.IO.IOException更合适。
b)私有数据是原始异常的一部分
在上面的例子中,假如文件路径包含在原始的System.IO.IOExceptionzhong ,就会暴露敏感的系统信息,所以应该使用其他异常类型来封装它(当然,前提是原始异常没有设置InnerException属性)。
c)异常类型过于具体,以至于调用者不能恰当的处理
例如,不要引发特定于某个数据库系统的异常。相反,可以使用一个较为广泛的异常,以避免在调用栈比较高的位置写特定于某个数据库的代码
自定义异常
- 使用Exception后缀
- 一般都应包含三个构造,无参构造,一个string参数构造和string加一个内部异常(包装成新异常时,提供额外信息)作为参数的构造
- 避免使用深的继承层次结构(一般小于5级)