C#中的异常机制主要依赖于几个关键字,try,catch,finally和throw。合理实现异常机制有利于程序健壮性,不至于在一个异常抛出就造成程序崩溃。
最简单的情况
C#使用try来包括可能会抛出异常的代码块,之后可以接catch,可以接finally,也可以接catch和finally,但是不能一个都没有。catch代码块能够处理抛出来的异常,而finally主要是为了执行一段无论异常抛出与否都会执行的代码。
try
{
int i = 10;
int j = 0;
int result = i / j;
}
catch
{
Console.WriteLine("error catch");
}
finally
{
Console.WriteLine("Finally");
}
这段代码会输出error catch和Finally因为有异常抛出,所以catch代码块中的代码被执行,同时finally代码块也会执行。
另一方面,如果我们把上面的j改为其他非0数字,没有异常抛出,那么只有finally代码块里面的代码会执行,因为无论有没有异常,finally都是确保执行的。
当return遇上finally
在使用finally的时候,有一种情况比较迷惑人,如果finally之前的try里面,有return,那么finally还执行吗?
理解这一点的关键,是要明白,finally有两个特性。
-
当函数堆栈没有退出的时候,Finally代码块跟其他代码块一样,正常执行。
-
当函数堆栈退出的时候,无论是因为异常抛出而退出还是使用return退出,finally都会确保执行,但是yield return不会触发finally。
理解了这两点,我们就很容易知道下面代码的行为。
void FinallyTest()
{
try
{
return;
}
finally
{
Console.WriteLine("finally executed");
}
}
FinallyTest(); //这次调用会输出finally executed
public static IEnumerable<string> GetStrings()
{
try
{
yield return "a";
yield return "b";
yield return "c";
yield return "d";
}
finally
{
Console.WriteLine("finally executed"); //在使用foreach遍历的时候,yield return 并没有触发finally代码块
}
}
为什么要使用throw
throw关键字用于重新抛出异常,通常当我们希望在不同的函数层次中依次处理一个异常的时候,我们就需要使用throw关键字,常见做法为,底层函数捕获异常,进行一些处理,之后抛出这个异常给上层函数,上层函数继续这个操作,这样不同层次的函数可以选择自己想要的异常处理操作。
void OutterMethod()
{
try
{
InnerMethod();
}
catch
{
Console.WriteLine("Caught in outer");
}
}
void InnerMethod()
{
try
{
int i = 10;
int j = 0;
int result = i / j;
}
catch(Exception e)
{
Console.WriteLine("Caught in inner");
throw;
}
}
在上面这段代码中,我们可以看到,innerMethod捕捉到了异常之后,进行了自己的处理再抛出,这样上层代码有机会再针对这个异常进行处理。使用throw的时候有两种方式,一种是throw,一种是throw exception,它们之间的区别在于:
-
throw保留之前的异常堆栈信息,而且不需要catch中的Exception实例。
-
throw e新建异常堆栈信息,这样堆栈信息的起始点就是throw e的地方而不是原始异常发生的地方。
根据不同的需求,选择适合的方式使用throw。