C#语言学习笔记11 —— 异常和异常处理
异常和异常处理
异常概述
从分类来说,异常可以由硬件产生(如除数为0),也可以由软件产生(如参数非法); 可以由内核模式下的代码产生,也可以由用户模式下的代码产生。 另外,异常还可以分为系统级别的异常和应用程序级别的异常。
和其它高级语言一样,C# 语言提供了一个结构化的、统一的、类型安全的异常处理机制。
对于 C# 语言,从代码角度,异常可来源于语言运行时(common language runtime,简称 CLR)、DotNet 自身、第三方库、应用代码本身。
C# 语言异常机制的语法
总的来说,C# 异常处理由语句(或子句) try、catch、finally、throw 等构成。
一个典型的 try 语句的语法如下所示:
异常的数据类型
C# 的异常是类型安全的,所有异常类型都直接或间接地从共同基类 System.Exception 派生。
常见的一些异常类型有:
System.ArithmeticException、
System.ArrayTypeMismatchException、
System.DivideByZeroException、
System.IndexOutOfRangeException、
System.InvalidCastException、
System.NullReferenceException、
System.OutOfMemoryException, 等等。
示例:捕获多个异常
try 子句后面可以跟多个 catch 子句,每个 catch 子句指定不同的异常类型。 捕获的时候,从前往后,依次匹配 catch 子句,条件满足即匹配成功,成功仅需一个。 因此,如果要捕获的多个异常类型,如果存在派生关系,需要把最具体的类型排在前面,最一般的类型排在后面。
示例:带有 finally 子句
try 语句可以带有一个 finally 子句。finally 子句是可选的,可以没有。 finally 子句又称终止器(termination handler),它总会被执行。 如果 try 不产生异常,执行完 try 的最后一个语句后,接着执行 finally 子句。 如果 try 产生异常,执行完对应的 catch 后,接着执行 finally 子句。 finally 常用来放置一些最后必须要执行的动作,比如释放资源等。
示例:抛出异常
使用 throw 语句抛出异常。"throw new <新的异常>;",创建新的异常并抛出; "throw;" 重新抛出当前异常。
例如,在函数中,根据条件判断,主动抛出异常:
又例如,在给属性赋非法值时抛出异常:
示例:使用自定义异常类型
要自定义一个异常类型,需要声明一个类,继承于 System.Exception,或继承于 System.Exception 的派生类。
大多数第三方类库都使用了自定义异常类型。
下面是一个使用自定义异常类型的简单例子:
示例:使用 when 异常过滤(exception filter)
在 catch 异常类型的时候,可以加上 when 表达式,当类型匹配且表达式返回 true 时,匹配成功。
同一个异常类型,可以加上不同的 when 表达式。
示例:在 async / await 中使用异常
当在 async / await 中使用异常时,需要注意一点,尽管异常可能已经在异步执行中产生,但只有在执行到 await 时, 产生的异常这时才传导过来。
示例:捕获所有异常
有些时候,我们需要在一个方法中捕获所有异常。 比如一个需要长时间运行的程序,希望不管发生什么异常,它都不终止运行。 这是,可以在应用入口函数中捕获所有异常。
为了捕获所有异常,可以采用 catch(Exception) 这种形式,因为 Exception 是所有异常的基类, 所以它能匹配所有的异常类型。或者,使用不带类型的 catch 子句。
异常机制和其它错误处理方式的比较
方式1、通过函数返回值或传出参数,传递错误信息
优点:
- 开销小,主要花在参数传递。
- 线程安全,可用于多线程环境。
缺点:
- 传递的方式不统一,需要文档辅助说明。
- 如果隔层处理错误,需要在错误处理层和错误产生层之间,层层传递同一个错误信息,比较繁琐、枯燥乏味。
方式2、通过类似全局变量的方式,传递错误信息
优点:
- 开销小,主要在参数传递,而且只传一次。
- 如果隔层处理错误,不需额外传递错误信息,可直接访问。
缺点:
- 非线程安全,很难甚至不能用于多线程环境。
- 就算是单线程、也容易因处理不当而产生错误。
- 只适合一些比较特殊的场景。
方式3、使用异常处理机制
优点:
- 处理方式统一,类型安全。
- 线程安全,可用于多线程环境。
- 错误产生和错误处理互不依赖,只需关心自己的需要,很方便。
缺点:
- 开销比参数传递要多一些,在异常产生时,需要保存一些调用现场,然后需要逐层检查调用堆栈,以便寻找匹配的异常处理入口。
因为异常机制有许多优点,开销其实也不算太大,实际中得到大量应用。在模块化软件设计中,更是普遍使用。所以,已经成为了一种标准。
结束语
C# 的异常处理,可以帮助开发人员方便、优雅、安全地开发软件。