21.3 捕获异常
可以使用try/catch块来捕获并选择是否处理异常,捕获了异常并非一定要处理异常,还可以将异常转换为另一个异常后再抛出,或者仅仅是记录异常的信息。程序代码和异常处理代码分到try块和catch块中,catch块是一系列以关键字catch开头的语句,语句后跟异常类型和要执行的操作,其中异常类型可以省略。
要记住,始终必须按从最特定到最不特定(或者换句话说就是从具体到一般)的顺序对catch块中处理的异常进行排序,如图21-3所示。这个原则可以保证在将某个特定异常传递给更一般的异常的catch块之前处理该异常。
图 21-3 对catch块中处理的异常进行排序
try/catch块有三种形式:try-catch、try-finally、try-catch-finally,不带有catch或finally块的try语句将导致编译器错误,如图21-4所示。
图 21-4 使用try语句捕获异常
try语句中的代码是可能抛出异常的代码,catch块捕捉某种特定的异常并加以处理。这些catch块可以有多个,并且catch块可以串联在一起,如果存在多个catch块,那么计算顺序是从顶部到底部。但是,对于所引发的每个异常,都将只执行一个catch块。
当异常发生后,CLR首先会判断当前引发的是何种异常类型,例如,是DivideByZeroException还是ArgumentException,然后以从上至下的顺序,搜索与当前执行语句(且和具体的异常类型)最匹配的catch块。搜索首先会从当前方法开始,寻找方法是否包括于一个try/catch块内,如果没有,就定位到调用当前方法的方法并继续寻找,这种搜索会一直向上层继续,直到找到可以处理且和当前异常最匹配的catch块为止。
如果存在两个catch块,一个是捕获某个特定的异常,一个是捕获更加常规的异常,这里有个优先级和顺序问题需要注意:
❑首先,捕获特定异常的匹配度要比捕获更加常规异常的匹配度高,因此,异常处理将由捕获特定异常的catch块执行。一般而言,好的编程做法是捕获特定类型的异常,而不是捕获更常规的异常。
❑如果捕获特定类型的catch块和捕获异常的基类型的catch块同时存在,则前者要位于后者之前,否则将无法通过编译。
接下来的示例代码将使用try/catch块捕获可能的异常。ExceptionSample类的Main方法包含调用DoSomething方法的try块。这里为了方便演示,简单地在DoSomething方法中抛出了一个OneException异常。其中,OneException派生自System.ApplicationException类型。跟在try块后面的是三个catch块,分别捕获OneException、System.ApplicationException、System.SystemException三种异常,如代码清单21-3所示。
代码清单21-3 捕获异常示例代码
namespace ProgrammingCSharp4
{
//自定义异常类型
class OneException:System.ApplicationException
{
}
class ExceptionSample
{
public static void Main()
{
try
{
ExceptionSample sample=new ExceptionSample();
sample.DoSomething();
}
catch(OneException e)
{
System.Console.WriteLine(“进入OneException异常处理代码段”);
}
catch(System.ApplicationException e)
{
System.Console.WriteLine(“进入System.ApplicationException异常处理代码段”);
}
catch(System.SystemException e)
{
System.Console.WriteLine(“进入System.SystemException异常处理代码段”);
}
finally
{
System.Console.WriteLine(“进入finally代码段”);
}
}
public void DoSomething()
{
throw new OneException();//显式引发异常
}
}
}
上述代码的运行结果为:
进入OneException异常处理代码段
进入finally代码段
在上述代码中,由于OneException派生自System.ApplicationException,而DoSomething方法抛出的是OneException异常,因此对OneException类型处理的catch语句将负责对此异常的处理过程。如果将对System.ApplicationException异常处理的catch语句放置在OneException的catch语句之前,将无法通过编译,编译器将提示如下错误信息:
上一个catch子句已经捕获了此类型或超类型
("System.ApplicationException")的所有异常
也就是说,由于ApplicationException是OneException的基类,因此捕获ApplicationException异常的catch语句将捕获ApplicationException和所有从它派生的异常,这将导致OneException异常的catch语句永远不会到达,因此CLR使用编译错误来提示这种可能的情形。
一般来说,除非明确知道如何处理try块中可能引发的所有异常,或者在catch块的末尾包括一条throw语句,否则,请不要在catch块中指定Exception。
如代码清单21-4所示,当我们不需要明确处理DoSomething方法引发的所有异常,并且也不需要将引发的异常通过throw关键字抛出到方法的上层调用时,在catch块中可以不包含某个特定的异常类型。
代码清单21-4 在catch块中不指定具体的异常
try
{
ExceptionSample sample=new ExceptionSample();
sample.DoSomething();
}
catch
{
System.Console.WriteLine(“有错误发生……”);
}