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


    异常处理,我们写的代码里经常会用到: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块中的代码执行完毕之后才执行。
  • 相关阅读:
    concurrent模块
    gevent模块
    Python中的线程
    2019.10.22 用TCP实现服务端并发接收
    Python中的进程
    进程和线程
    网络编程常用模块及方法
    [UOJ #222][NOI2016]区间(线段树)
    [BZOJ 4873][SHOI&SXOI2017]寿司餐厅(最大权闭合子图)
    [BZOJ 3751][NOIP2014]解方程(哈希)
  • 原文地址:https://www.cnblogs.com/kdalan/p/2537498.html
Copyright © 2020-2023  润新知