一道异常处理执行顺序面试题的简单分析

异常处理,我们写的代码里经常会用到:try{}catch{}finally{}。可是大家真的了解它吗?

下面的代码,运行结果是什么?大家猜一下:

View Code 
    static class Program
    {

        static void Main(string[] args)
        {
            Console.WriteLine(Program.MethodC());
            Program.MethodB();
            Console.ReadLine();
        }

        static void MethodA()
        {
            try
            {
                throw new NullReferenceException();
            }
            catch (IndexOutOfRangeException)
            {
                throw;
            }
            finally
            {
                Console.WriteLine("MethodA finally");
            }
        }

        static void MethodB()
        {
            try
            {
                MethodA();
            }
            catch (NullReferenceException)
            {
                Console.WriteLine("MethodB catch");
            }
            finally
            {
                Console.WriteLine("MethodB finally");
            }
        }

        static int i = 1;
        static int MethodC()
        {
            try
            {
                Console.WriteLine("MethodC try");
                return i;
            }
            finally
            {
                i = 2;
                Console.WriteLine("MethodC finally");
                Console.WriteLine("MethodC:"+i);
            }
        }

 下面给出运行结果:

View Code 
MethodC try
MethodC finally
MethodC:2
1
MethodA finally
MethodB catch
MethodB finally

 看上面的运行结果,语句:Console.WriteLine(Program.MethodC())的输出为:

MethodC try
MethodC finally
MethodC:2
1

MethodC方法,主要考察的有二点,一是finally语句和return语句执行的先后顺序,二是finally语句是否可以改变return语句中返回的值。
第一点大家都知道,return前finally总是会执行,第二点就有些模糊了,运行结果也跟最初自己猜的不同。我们看一下MethodC方法生成的IL代码:

.method private hidebysig static int32  MethodC() cil managed
{
  // Code size       70 (0x46)
  .maxstack  2
  .locals init (int32 V_0)
  IL_0000:  nop
  .try
  {
    IL_0001:  nop
    IL_0002:  ldstr      "MethodC try"
    IL_0007:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_000c:  nop
    IL_000d:  ldsfld     int32 ConsoleApplication.Program::i  //将静态字段Program.i压入栈中
    IL_0012:  stloc.0  //从栈中取出值(就是刚压入的i),放到"第0号"临时变量中
    IL_0013:  leave.s    IL_0043  //这里会退出try区块,转向IL_0043
  }  // end .try
  finally
  {
    IL_0015:  nop
    IL_0016:  ldc.i4.2  //在栈中放入一个4byte的数,值为2
    IL_0017:  stsfld     int32 ConsoleApplication.Program::i  //从栈中获取值(刚放入的2),修改i
    IL_001c:  ldstr      "MethodC finally"
    IL_0021:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_0026:  nop
    IL_0027:  ldstr      "MethodC:"
    IL_002c:  ldsfld     int32 ConsoleApplication.Program::i
    IL_0031:  box        [mscorlib]System.Int32
    IL_0036:  call       string [mscorlib]System.String::Concat(object,
                                                                object)
    IL_003b:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_0040:  nop
    IL_0041:  nop
    IL_0042:  endfinally
  }  // end handler
  IL_0043:  nop
  IL_0044:  ldloc.0  //将"第0号"临时变量的值压入栈中
  IL_0045:  ret  //退出方法,返回值
// end of method Program::MethodC

  注意红色字体部分,绿色注释是我添加的,从上面IL及注释可以了解到,在MethodC方法里,会有一个隐式的”第0号“变量,来临时保存return的值,所以finally中语句虽然修改了Program.i的值,但是MethodC方法的返回值是不因finally语句而变化。

Program.MethodB(); 的输出结果,没什么好说的,大家应该都可以准确的说出来,这里只引用CLR VIA C#上一段话将try catch 语句的执行顺序简单介绍一下:
  在try块中的代码(或者从try块调用的任何方法)抛出一个异常,CLR将搜索捕捉类型与抛出的异常相同(或是它的基类)的catch块。如果没有任何捕捉类型与抛出的异常匹配,CLR会去调用栈的更高一层搜索一个与异常匹配的捕捉类型。如果到了调用栈的顶部,还是没有找到具有匹配捕捉类型的一个catch块,就会发成一个未处理的异常。
  一旦CLR找到一个具有匹配捕捉类型的catch块,就会执行内层所有finally块中的代码。所谓“内层finally块”是指从抛出异常的try块开始,到匹配异常的catch块之间的所有finally块。这里注意匹配异常的那个catch块所关联的finally块尚未执行,该finally块的代码一直要等到这个catch块中的代码执行完毕之后才执行。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章