一道異常處理執行順序面試題的簡單分析

異常處理,我們寫的代碼裏經常會用到: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塊中的代碼執行完畢之後才執行。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章