异常处理
在C#中,所有的异常都是使用一个异常类型的实例对象表示,都继承自System.Exception类型,或者直接使用System.Exception类型的实例对象;位于finally块中的代码可以保证不管代码是正常结束,还是进入异常处理代码块,其中的语句均会被执行,finally语句中放置资源回收的代码是一个不错的主意;BCL都提供了预定义的异常类供使用,其使用方式和应用级别的异常处理完全相同。
异常类
System.Exception类是所有异常类的基类,其中:
- Message:只读字符串,此属性为当前的异常提供了描述性信息
- InnerException:只读属性,值不为null,则可以通过它的值获取当前异常的异常实例;如果其值为null,则表示当前异常不是由其他异常引发的
- StackTrace:只读字符串,描述了调用堆栈的内容,其中首先显示最近的方法调用
为了区分系统级异常和应用级异常,BCL提供了两种直接继承自System.Exception的异常类型,System.SystemException和System.ApplicationException。因此我们编写的自定义异常应从ApplicationException继承。
抛出异常
public long Divide(int a, int b)
{
try
{
return a / b;
}
catch (System.DivideByZeroException e)
{
//抛出异常
//如果有原始异常,推荐将原始异常传入
throw new System.ArgumentException(“除数不能为0”, “b”, e);
}
}
捕获异常
处理异常时都按照从特定到最不特定(从具体到一般)的顺序对catch块中处理的异常进行排序,try/catch块有三种形式:try-catch、try-finally、try-catch-finally,不带catch或finally块的try语句将导致编译器错误。
try {} //可能引发异常的代码
catch (System.ApplicationException e) {} //异常处理代码块,可以有多个
catch (System.SystemException e) {} //顺序从具体到一般
finally {} //不管是否发生异常,均会执行
当异常发生后,CLR首先会判断当前引发的是何种异常类型,例如:是DivideByZeroException还是ArgumentException,然后以从上至下的顺序,搜索与当前执行语句(且和具体的异常类型)最匹配的catch块。搜索会从当前方法开始,寻找方法是否包括于一个try/catch块内,如果有,就对catch进行匹配,如果没有就定位到调用当前方法并继续寻找,一直向上层继续,直到找到为止。
如果存在两个catch块,一个是捕获某个特定的异常,一个是捕获更加常规的异常,注意:
- 捕获特定异常的匹配度要比捕获常规异常的匹配度高
- 捕获特定类型的catch块和基类型的catch块同时存在,则前者要位于后者之前,否则无法通过编译
class OneException : ApplicationException {}
class Program
{
static void DoSomething()
{
throw new OneException();
}
static async Task Main(string[] args)
{
try
{
Program.DoSomething();
}
catch (OneException e)
{
//执行
Console.WriteLine("OneException code");
}
catch (ApplicationException e)
{
//ApplicationException是OneException的基类,同时出现时必须放在它的后面,否则会编译错误
Console.WriteLine("ApplicationException code");
}
catch (SystemException e)
{
Console.WriteLine("SystemException code");
}
finally
{
//执行
Console.WriteLine("finally code");
}
}
}
一般来说,除非明确知道如何处理try块中可能引发的所有异常,或者catch块的末尾包括一条throw语句,否则请不要在catch块中指定Exception,
自定义的异常
自定义的异常应该从System.Application.Exception派生,创建时要注意:
- 从System.Application.Exception和System.Exception派生
- 使用Exception这个词作为自定义的异常名称为后缀
- 至少提供三个公共构造函数(一个不包含参数的默认构造函数、一个可以包含异常消息的构造函数、一个可以包含异常消息,以及引发异常的异常引用的构造函数)
异常处理方法
- 尽量先由程序自动处理异常,若不能则考虑使用友好的提示告知用户,并根据等级记录日志
- 限定异常范围,尽量缩小异常处理范围,如果只需要检测某一行代码,就不要把整段代码都放进try语句中
- 应该尽量在上层捕获并处理异常(全局异常处理),过多的下层处理会有性能损失