2013年10月16日 星期三

(MSIL) Microsoft Intermediate Language

在文章開始前 首先必須逐步讓讀者清楚明白
什麼是IL?? MSIL??  首先MSIL是簡稱
全名是( Microsoft Intermediate Language )
中文是"中間語言"! 那一定會問什麼是中間語言?

MSDN解釋如下
編譯器會將您的原始程式碼轉譯成 Microsoft Intermediate Language (MSIL),它是可以有效率地轉換為機器碼而與 CPU 無關的指令集。MSIL 包括可用來載入、儲存、初始化和呼叫物件上方法的指令,以及用於數學和邏輯作業、控制流程、直接記憶體存取、例外處理和其他作業的指令。在程式碼可以執行之前,必須將 MSIL 轉換為 CPU 特定程式碼,而此轉換通常是藉 Just-In-Time (JIT) 編譯器進行。

本章不做架構上的深層探討,我們將直接進入主題



首先 先來一個簡單的範例當開端
我們建立一個方法(帶入參數i與j)
經過方法內部簡易運算後,並傳回r 的整數int
方法如下

public int Sub(int i,int j)
        {
            int s;
            int t = 0;
            int r = 4;
            s=i;
            r =i – j;
            r + =s + t;// r+s+t
            return r;
        }


並使用ILDASM.exe來觀察SUB:int32(int32,int32)中的IL碼

在介紹開始前 首先來簡單說明一下IL基礎規則

以點號’.’開頭的標號為偽指示代碼,只起指示作用,最終不會被JIT編譯為本地可執行代碼,如“.method”,“.locals”等。而不帶點號’.’的標號為IL匯編代碼,它們在運行時將會被JIT編譯為本地可執行代碼,如“ldarg.1”等。

首先,讓我們看一看函數體內的第一條語句:.maxstack 3。從其本身我們也可以猜出該語句說明堆棧的大小。暫且不表,且看下文。
 
第二句:.locals init ([0] int32 s,[1] int32 t,[2] int32 r,[3] int32 CS$1$0000) 。[0]、[1]、[2]和我們在CS源程序中定義的局部變量str一一對應,我們大概也能猜到這一句區域變數初始化的工作,但是奇怪的是怎麼會有4個,多了一個[3] int32 CS$1$0000?我們明明只定義了三個變數的。那麼這由C#編譯器自動維護的第四個變數有何作用?也暫且不表,先看下文。


IL程式碼如下


 .method public hidebysig instance int32  Sub(int32 i,
                                             int32 j) cil managed
{
  // Code size       23 (0x17)
  .maxstack  3
  .locals init ([0] int32 s,
           [1] int32 t,
           [2] int32 r,
           [3] int32 CS$1$0000)
  IL_0000:  nop//NOP指令(這個指令的意思是啥都不做, 也就是空)資料來源
 
  IL_0001:  ldc.i4.0//這條語句作用是在堆疊中載入常數,i4表示該常數為雙字長的32位整型數,
                                //初始    值為0。“ldc”可以理解為“load constant
 
  IL_0002:  stloc.1//這條語句作用是將上一句堆疊頂元素0存入第一個區域變數所以 "int t=0"。
                               //’1’表示t第一個區域變數。“stloc”可以理解為“store to local”,保存區域變數
 
  IL_0003:  ldc.i4.4//如同 IL_0001在堆疊中放入常數32bit整數型態"4"
 
  IL_0004:  stloc.2//並且存取位置,存入局部變數2  因此"int r=4"
 
  IL_0005:  ldarg.1//Idarg的意思是load argument載入參數到堆疊中這裡指的參數是方法中的參數
                                 //不同的是索引值視從1開始所以 1代表
                                 //public int Sub(int i,int j)中的i
  IL_0006:  stloc.0//將上一句堆疊中的i放到區域變數中的s,所以"s=i"
  IL_0007:  ldarg.1//
  IL_0008:  ldarg.2//ldarg.1,ldarg.2這兩句參數代表public int Sub(int i,int j)
                             //方法中的i與j 依造先進後出堆疊順序一起放入堆疊中
 
  IL_0009:  sub//將  ldarg.1 推入至堆疊。將  ldarg.2 推入至堆疊。
                        //將  ldarg.2和  ldarg.1 從堆疊取出;
                        //將 ldarg.1減去  ldarg.2將結果推入至堆疊。

  IL_000a:  stloc.2 //將上述存入r 所以意思是 r=i-j

  IL_000b:  ldloc.2//r
  IL_000c:  ldloc.0//s
  IL_000d:  ldloc.1//t
                              //這三句代表著load local variable載入區域變數
                              //不同於ldc.i4.0在堆疊中存入0
                              //stloc.1 再將0存入t
                              // ldloc意思是 單純讀取變數放入堆疊中
  IL_000e:  add //t+s 存入堆疊1
  IL_000f:  add//r+=(t+s) //存入堆疊2
  IL_0010:  stloc.2//r=(堆疊1+2)
  IL_0011:  ldloc.2//將r值推入堆疊
  IL_0012:  stloc.3//將上述r值存入[3] int32 CS$1$0000
  IL_0013:  br.s       IL_0015//跳轉至IL_0015
  IL_0015:  ldloc.3//將ldloc.3值 推入堆疊
  IL_0016:  ret//並且返回值 ldloc.3
} // end of method Program::Sub

變量有RST但是IL程式碼中卻有0~3, 4個變數
那為何 要再多一個3變數來存取R呢??
綜觀圖  IL_0000~  IL_0016,我們發現,整個函數過程所用到的最大棧數目就是3,這也就不難理解第一條語句 .maxstack  3  了。
回到原問題點 3 有何意義??
IL程式碼倒數到2個指令
ldloc.3 其實可以用 ldloc.2代替阿  幹馬要多一個??
因為我們要的結果已經是 變數r ldloc.2 了
這樣不是讓費空間嗎?
注意的是
我們在呼叫副程式時
並不是所又返回值 都是 城市區塊中的變數
有可能是帶入的參數 或類別中的變數
請看下列範例


public int Laxi(int x)

              {

                     return x;         //直接將參數返回 在程式區塊裝並無變數

              }
int age;...

        public int GetAge()

        {

                     return age;      //返回在類中定義的字段

        }
或者是直接返回一個表達式:

              public int GetInteger()

              {

                     return age+4*6/2;

              }
必須進行這一步轉換,
以return r為例:ldloc.x -> stloc.y ->ldloc.y ->ret .
 因為在以上三種情況當中,返回值都不存儲在局部變量當中。


OK!有了上述的教學也有了初步對IL的認知 我們來看下一個例題
這次來瞧瞧 switch的IL長啥樣??

首先switch範例請看下面!

static void  Sub(int n)
        {
            switch(n) 
            { 
                case 1: Console.WriteLine("One"); break; 
                case 2: Console.WriteLine("Two"); break; 
                case 3: Console.WriteLine("Three"); break; 
            } 
        }



    把範例看清楚後, 接下來我們來看IL碼

    .method private hidebysig static void  Sub(int32 n) cil managed
    {
      // Code size       65 (0x41)
      .maxstack  2
      .locals init ([0] int32 CS$4$0000)//這裡的0指的是"n"
      IL_0000:  nop//不做任何事
      IL_0001:  ldarg.0//將方法中的n 推入堆疊中,奇怪的是這範例索引卻是0開始
                                  // 目前不知道索引值是不是一定是0開始,這裡備查往後推理用
      IL_0002:  stloc.0//放如常數n中
      IL_0003:  ldloc.0//讀取n數值 放入堆疊
      IL_0004:  ldc.i4.1//堆疊中放入1
      IL_0005:  sub//將上面兩個堆疊相減"1-n"

      //在這裡 答案不是case 中的1~3而是一組索引值0~2
      IL_0006:  switch     (
                            IL_0019,   //索引0 呼叫 IL_0019
                            IL_0026,  //索引0 呼叫 IL_0026
                            IL_0033)  //索引0 呼叫 IL_0033
      IL_0017:  br.s       IL_0040//直接跳到IL_0040: ret 傳回值

      IL_0019:  ldstr      "One" //將字串的物件參考推入至堆疊。
      IL_001e:  call       void [mscorlib]System.Console::WriteLine(string)
                                   //將方法引數從 arg1argN 自堆疊取出;
                                   //使用這些引數來執行方法呼叫,且控傳輸制項至方法
                                   //描述項所參考的方法。完成時,被呼叫端方法會產生傳回值
                                  //並傳送至呼叫端。
      IL_0023:  nop
      IL_0024:  br.s       IL_0040//條轉IL_0040 傳回值 並show出WriteLine(string)

       //以下同上
      IL_0026:  ldstr      "Two"
      IL_002b:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_0030:  nop
      IL_0031:  br.s       IL_0040
      IL_0033:  ldstr      "Three"
      IL_0038:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_003d:  nop
      IL_003e:  br.s       IL_0040
      IL_0040:  ret
    } // end of method Program::Sub

    再來討論一個有趣的問題
    呈上題 swtich中的case 是1~3
    但在IL中是0~2

    那我們再看一題,把swtich中的case改為1,3,5
    奇怪的是IL內的索引不是0,2,4
    而是0,1,2,3,4 先不談為何!

    我們先來看 程式碼例題

    static void  Sub(int x,int y,int s)
            {
                switch(s) 
                { 
                    case 1: Console.WriteLine("1"); break; 
                    case 3: Console.WriteLine("3"); break; 
                    case 5: Console.WriteLine("5"); break;
     
                } 
            }

    case 取直為 1,3,5 OK沒問題後我們看IL

    .method private hidebysig static void  Sub(int32 x,
                                               int32 y,
                                               int32 s) cil managed
    {
      // Code size       73 (0x49)
      .maxstack  2
      .locals init ([0] int32 CS$4$0000)
      IL_0000:  nop
      IL_0001:  ldarg.2//參數s
      IL_0002:  stloc.0//把上面s參數放入區域變數中堆疊中
      IL_0003:  ldloc.0//讀取區域變數s
      IL_0004:  ldc.i4.1//在堆疊中放入int 1
      IL_0005:  sub// s-1= value
      IL_0006:  switch     (
                            IL_0021,//0
                            IL_0048,//1  ,s-1 =1的話直接跳到REF
                            IL_002e,//2
                            IL_0048,//3  ,s-1 =3的話直接跳到REF
                            IL_003b //4
                                       )
      IL_001f:  br.s       IL_0048 //跳轉0048的 ret返回
      IL_0021:  ldstr      "1"//將字串的物件參考推入至堆疊
      IL_0026:  call       void [mscorlib]System.Console::WriteLine(string)//呼叫WriteLine(string)
      IL_002b:  nop
      IL_002c:  br.s       IL_0048 //跳轉0048的 ret返回
      IL_002e:  ldstr      "3"
      IL_0033:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_0038:  nop
      IL_0039:  br.s       IL_0048 //跳轉0048的 ret返回
      IL_003b:  ldstr      "5"
      IL_0040:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_0045:  nop
      IL_0046:  br.s       IL_0048 //跳轉0048的 ret返回
      IL_0048:  ret//返回值
    } // end of method Program::Sub

    因為S-1=IL中的索引值
    也就是說,對於取直不連續的狀況
    編輯器會自動“default子句”的地址來填充switch指令中的“缝隙”。

    那麼如果cass中設定10,20,50
    那是不是就會產生 更多的縫隙更多的程序呢??
    來看聰明的編輯器如何克服此問題

    先來看一下 範例程式
    static void  Sub(int x,int y,int s)
            {
                switch(s) 
                { 
                    case 10: Console.WriteLine("10"); break; 
                    case 30: Console.WriteLine("30"); break; 
                    case 50: Console.WriteLine("50"); break;
                } 
            }

    接下來看 IL程式碼
    .method private hidebysig static void  Sub(int32 x,
                                               int32 y,
                                               int32 s) cil managed
    {
      // Code size       60 (0x3c)
      .maxstack  2
      .locals init ([0] int32 CS$4$0000)
      IL_0000:  nop
      IL_0001:  ldarg.2//將方法變數S放入堆疊
      IL_0002:  stloc.0//將S值推入區域變數堆疊中
      IL_0003:  ldloc.0//讀取上述,區域變數中的值
      IL_0004:  ldc.i4.s   10 //ldc.i4.s代表int 8位元-128~127之前 將10放入堆疊
      IL_0006:  beq.s      IL_0014 //beq.s 意思是比對前兩堆疊中的數值是否相等
                                                    //如果 ldloc.0 (S值) = ldc.i4.s (10) 跳轉IL_0014
      IL_0008:  ldloc.0
      IL_0009:  ldc.i4.s   30
      IL_000b:  beq.s      IL_0021
      IL_000d:  ldloc.0
      IL_000e:  ldc.i4.s   50
      IL_0010:  beq.s      IL_002e
      IL_0012:  br.s       IL_003b
      IL_0014:  ldstr      "10"//將10字串的物件參考推入至堆疊
      IL_0019:  call       void [mscorlib]System.Console::WriteLine(string)//呼叫程式帶入10
      IL_001e:  nop
      IL_001f:  br.s       IL_003b//跳轉去ret 返回
      IL_0021:  ldstr      "30"
      IL_0026:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_002b:  nop
      IL_002c:  br.s       IL_003b
      IL_002e:  ldstr      "50"
      IL_0033:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_0038:  nop
      IL_0039:  br.s       IL_003b
      IL_003b:  ret
    } // end of method Program::Sub

    當switch語句的取值非常不連續時,
    编译器會放棄使用switch指令,
    改用一系列跳轉來實現。
    還有一點像是if-else if-...-else語句。

    接下來我們來探討,如果switch(不是int32而是string)
    那們IL會如何處裡字串呢?

    首先來看範例程式碼

    static void gogostring(string x)
            {
                switch(x)
                {
                    case null: Console.WriteLine("unll");
                        break;
                    case "one": Console.WriteLine("one");
                        break;
                    case "two": Console.WriteLine("two");
                        break;
                    case "three": Console.WriteLine("three");
                        break;
                    case "four": Console.WriteLine("four");
                        break;
                    default: Console.WriteLine("onknown");
                        break;
                }
                Console.WriteLine("After switch.");
            }


    再來看看IL


    .method private hidebysig static void  gogostring(string x) cil managed
    {
      // Code size       150 (0x96)
      .maxstack  2
      .locals init ([0] string CS$4$0000)
      IL_0000:  nop
      IL_0001:  ldarg.0//將X推入堆疊
      IL_0002:  stloc.0//將上面X值堆疊取出 放入區域變數的X中
      IL_0003:  ldloc.0//讀取堆疊中區域變數的X
      IL_0004:  brfalse.s  IL_003c// 如果value值是FALSE跳轉到 IL_003c SHOW出 NULL值
                      //如果 value (屬於型別 int32int64、物件參考 O、Managed 指標 &
                      //暫時性指標 *native int) 那麼value值為零 (false)
                      //則 brfalse.s 指令 (及其別名 brnullbrzero)
                      //將控制權傳輸至指定的目標指令。如果 value 為非零 (true),
                      //則在下一個指令繼續執行。
                      //如果是字串 就是true 繼續往下執行唷!!


      IL_0006:  ldloc.0 //取區域變數推入堆疊
      IL_0007:  ldstr      "one" //load string 讀取字串
      IL_000c:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                     string)
                                    //傳回值是bool  而Equality(string,string)
                                    // 我們可以知道經由比對,如果傳回true
                                   //下一條指令brtrue.s  如果是true將會跳轉到  IL_0049
                                   //如同case子句

                                    //下面0011~0038
      IL_0011:  brtrue.s   IL_0049
      IL_0013:  ldloc.0
      IL_0014:  ldstr      "two"
      IL_0019:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                     string)
      IL_001e:  brtrue.s   IL_0056
      IL_0020:  ldloc.0
      IL_0021:  ldstr      "three"
      IL_0026:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                     string)
      IL_002b:  brtrue.s   IL_0063
      IL_002d:  ldloc.0
      IL_002e:  ldstr      "four"
      IL_0033:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                     string)
      IL_0038:  brtrue.s   IL_0070

      IL_003a:  br.s       IL_007d //default 代表上面條件都不符合
                                                  //跳去IL_007d default子句
      IL_003c:  ldstr      "unll" //null
      IL_0041:  call       void [mscorlib]System.Console::WriteLine(string)//呼叫Writeline(放入NULL)
      IL_0046:  nop
      IL_0047:  br.s       IL_008a//跳轉到IL_008a 準備印出下一句

      IL_0049:  ldstr      "one" //推入字串
      IL_004e:  call       void [mscorlib]System.Console::WriteLine(string)//呼叫WriteLine讀取字串
      IL_0053:  nop
      IL_0054:  br.s       IL_008a //跳去IL_008a
      IL_0056:  ldstr      "two"
      IL_005b:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_0060:  nop
      IL_0061:  br.s       IL_008a
      IL_0063:  ldstr      "three"
      IL_0068:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_006d:  nop
      IL_006e:  br.s       IL_008a
      IL_0070:  ldstr      "four"
      IL_0075:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_007a:  nop
      IL_007b:  br.s       IL_008a
      IL_007d:  ldstr      "onknown" //default子句
      IL_0082:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_0087:  nop
      IL_0088:  br.s       IL_008a

      IL_008a:  ldstr      "After switch." //堆疊推入"After switch"
      IL_008f:  call       void [mscorlib]System.Console::WriteLine(string)//呼叫並帶入After switch
      IL_0094:  nop
      IL_0095:  ret         //完成後傳回指令
    } // end of method Program::gogostring


    再來我們在比例題改一下
    Console.WriteLine(1~5)
    然後 case ONE~five
    範例如下


    static void gogostring(string x)
            {
                switch(x)
                {
                    case null: Console.WriteLine("unll");
                        break;
                    case "one": Console.WriteLine(1);
                        break;
                    case "two": Console.WriteLine(2);
                        break;
                    case "three": Console.WriteLine(3);
                        break;
                    case "four": Console.WriteLine(4);
                        break;
                    case "five": Console.WriteLine(5);
                        break;
                    default: Console.WriteLine("onknown");
                        break;
                }
                Console.WriteLine("After switch.");
            }


    再來看一下IL

    .method private hidebysig static void  gogostring(string x) cil managed
    {
      // Code size       216 (0xd8)
      .maxstack  4
      .locals init ([0] string CS$4$0000,
               [1] int32 CS$0$0001)
      IL_0000:  nop
      IL_0001:  ldarg.0 //取方法中X變數推入堆疊
      IL_0002:  stloc.0 // 將上面X推入堆疊的值取出,放入區域變數中
      IL_0003:  ldloc.0 //讀取 區域變數X值
      IL_0004:  brfalse.s  IL_0085 //判斷 X值是否為false
                                                    //brfalse.s判斷是否為NULL 因為方法限定STRING 除非我在
                                                    //呼叫副程式gogostring(輸入NULL)           
                                                    //除非自己輸入NULL不然不會是NULL
                                                    //一定都會跳到default 輸出 onknown
                       

      IL_0006:  volatile. // 可以隨意修改的修飾詞  表示後面的ldsfld指令要載入的字段volatile.
                       //ldsfld 載入靜態字段  是一個類別
                       //System.Collections.Generic.Dictionary`2<string,int32>字典類型
                       //可用來查詢語句 比如說新增(ONE,1) 如果搜尋1 會得到ONE
                       //注意緊接在後的是類別名稱<PrivateImplementationDetails>
                       //看來是編譯器自己建立的
                       //類別不是用戶建立的
                       //跨號後面0D06B917-7243-41BC-96F6-D7AB296FE13B} 是一個GUID
                       //代表系統建立一個模組,而最後是檔案名稱$method0x6000017-1'
                       //也就是說此字段使用字典命名空間載入字段為 類別名::檔案名
                       //下圖 可以看到  字典檔的 命名空間 請看圖
                       //<PrivateImplementationDetails>點開來 裡面有$method0x6000017-1'
                       //尾隨在後 就是我們加入的字典方法
                       //系統已成功將字段載入堆疊中 並用volatile修飾









     IL_0008:  ldsfld     class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32> '<PrivateImplementationDetails>{0D06B917-7243-41BC-96F6-D7AB296FE13B}'::'$$method0x6000017-1'


      IL_000d:  brtrue.s   IL_0058// 判斷ldsfld 中的field 有無字典
                                                   //System.Collections.Generic.Dictionary`2<string,int32>
                                                   //才剛新建所以沒有字點檔 一定是NULL繼續往下執行

      IL_000f:  ldc.i4.5 //將int 5推入堆疊
      IL_0010:  newobj     instance void class [mscorlib]  System.Collections.Generic.Dictionary`2<string,int32>::.ctor(int32) //new obj實體化字典物件
                                                                                       //ctor(5) 5個字典 0~4
    //開始建立字典檔
      IL_0015:  dup
      IL_0016:  ldstr      "one" //string 字典檔
      IL_001b:  ldc.i4.0  //int 0
      IL_001c:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0,
                                                                                                                    !1)
                                            //由先進後出順序
                                            //呼叫方法帶入0是int KEY值,1是字串
                                            //字典建立好了  所以輸入1 會得到ONE 下面4個以此類推
                                           //語法參考如下
                                           //Add (TKey key,TValue value)
                                           //key  要加入的元素的索引鍵。
                                           //value要加入之項目的值。
                                           //對於參考型別,值可以為 Null 參照 (即 Visual Basic 中的 Nothing)。

                                          //下面two three four...同上

      IL_0021:  dup
      IL_0022:  ldstr      "two"
      IL_0027:  ldc.i4.1
      IL_0028:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0,
                                                                                                                    !1)
      IL_002d:  dup
      IL_002e:  ldstr      "three"
      IL_0033:  ldc.i4.2
      IL_0034:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0,
                                                                                                                    !1)
      IL_0039:  dup
      IL_003a:  ldstr      "four"
      IL_003f:  ldc.i4.3
      IL_0040:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0,
                                                                                                                    !1)
      IL_0045:  dup
      IL_0046:  ldstr      "five"
      IL_004b:  ldc.i4.4
      IL_004c:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0,
                                                                                                                    !1)
    //字典新增完畢

      IL_0051:  volatile.
      IL_0053:  stsfld     class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32> '<PrivateImplementationDetails>{0D06B917-7243-41BC-96F6-D7AB296FE13B}'::'$$method0x6000017-1'

    //stsfld 先將字點檔推入堆疊 並取出存入field 方法中
    //因此現在有字典了!! 

      IL_0058:  volatile.
      IL_005a:  ldsfld     class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32> '<PrivateImplementationDetails>{0D06B917-7243-41BC-96F6-D7AB296FE13B}'::'$$method0x6000017-1'
    //005a 再次將field推入堆疊中 這次推入了字典

      IL_005f:  ldloc.0 //載入區域變數0 是一個STRING  CS$4$0000,
              
      IL_0060:  ldloca.s   CS$0$0001 //是一個整數型別 [1] int32 CS$0$0001)

      IL_0062:  call       instance bool class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::TryGetValue(!0,
                                                                                                                            !1&)
                           //0062 呼叫TryGetValue(字串,數值)
                           //這方法語法如下
                           //public bool TryGetValue(TKey key,out TValue value)
                           //TryGetValue(查詢索引,查詢結果的字串)
                           //所以IL_0060: ldloca.s 整數值 用於存放結果0~4


      IL_0067:  brfalse.s  IL_00bf//結果如果是false 則 跳到default字段
      IL_0069:  ldloc.1// 載入 結果字段 推入堆疊
      IL_006a:  switch     (
                            IL_0092,//0
                            IL_009b,//1
                            IL_00a4,//2
                            IL_00ad,//3
                            IL_00b6)//4
      IL_0083:  br.s       IL_00bf

      IL_0085:  ldstr      "unll" //如果user輸入值為null 將會跳轉到這
      IL_008a:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_008f:  nop
      IL_0090:  br.s       IL_00cc

      IL_0092:  ldc.i4.1 //將int 1推入堆疊
      IL_0093:  call       void [mscorlib]System.Console::WriteLine(int32) //呼叫WriteLine(1)
      IL_0098:  nop
      IL_0099:  br.s       IL_00cc// 跳轉 並將"After switch"字串 一併輸出

      IL_009b:  ldc.i4.2
      IL_009c:  call       void [mscorlib]System.Console::WriteLine(int32)
      IL_00a1:  nop
      IL_00a2:  br.s       IL_00cc
      IL_00a4:  ldc.i4.3
      IL_00a5:  call       void [mscorlib]System.Console::WriteLine(int32)
      IL_00aa:  nop
      IL_00ab:  br.s       IL_00cc
      IL_00ad:  ldc.i4.4
      IL_00ae:  call       void [mscorlib]System.Console::WriteLine(int32)
      IL_00b3:  nop
      IL_00b4:  br.s       IL_00cc
      IL_00b6:  ldc.i4.5
      IL_00b7:  call       void [mscorlib]System.Console::WriteLine(int32)
      IL_00bc:  nop
      IL_00bd:  br.s       IL_00cc
      IL_00bf:  ldstr      "onknown"
      IL_00c4:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_00c9:  nop
      IL_00ca:  br.s       IL_00cc

      IL_00cc:  ldstr      "After switch."// 輸出端 一定會連同此句一併輸出 所以一定會經過這裡
      IL_00d1:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_00d6:  nop
      IL_00d7:  ret //傳回數值
    } // end of method Program::gogostring A

    接下來 我們來看foreach的IL

    程式碼如下

    static void ILforeach(int[] x)
            {
                foreach (int E in x)
                {
                    Console.WriteLine(E);
                }
            }


    來看IL程式碼
    .method private hidebysig static void  ILforeach(int32[] x) cil managed
    {
      // Code size       36 (0x24)
      .maxstack  2
      .locals init ([0] int32 E,  //V0   foreach中的E
               [1] int32[] CS$6$0000, //V1 方法中的x[]的副本
               [2] int32 CS$7$0001,  //V2  用於循環用
               [3] bool CS$4$0002)  //V3  判斷是否中斷循環用
      IL_0000:  nop
      IL_0001:  nop
      IL_0002:  ldarg.0 //方法中的x[] 推入堆疊
      IL_0003:  stloc.1 //將上面x[]堆疊取出 並放入V1副本
      IL_0004:  ldc.i4.0 //堆疊中放入整數0
      IL_0005:  stloc.2 //將堆疊取出 放入區域變數V2
      IL_0006:  br.s       IL_0019 //跳去0019

      IL_0008:  ldloc.1// V1
      IL_0009:  ldloc.2//V2
      IL_000a:  ldelem.i4//將物件參考 array (V1=S[])推入至堆疊。
                                     //將索引值 index(V2=0) 推入至堆疊。
                                     //從堆疊填入 indexarray;會查詢儲存於 arrayindex 位置中的值。
                                     //將 index 中的值推入至堆疊。
     
                                     //ldelem.i4 指令會使用以零起始的一維陣列 array 中的索引
                                     //index (型別 native int) 載入元素的值,並將它放置在堆疊的頂端。
                                     //陣列是一個物件,因此可以由型別 O 的值來表示。


      IL_000b:  stloc.0 //將上面結果 存入V0
      IL_000c:  nop
      IL_000d:  ldloc.0 //讀取V0的結果
      IL_000e:  call       void [mscorlib]System.Console::WriteLine(int32)//呼叫WriteLine(V0)
      IL_0013:  nop
      IL_0014:  nop
      IL_0015:  ldloc.2 //讀取V2
      IL_0016:  ldc.i4.1//將1推入堆疊
      IL_0017:  add//V2=V2+1 累加V2
      IL_0018:  stloc.2//回存V2的值

      IL_0019:  ldloc.2//V2
      IL_001a:  ldloc.1//V1
      IL_001b:  ldlen // 將上面V1陣列物件取出堆疊 並計算長度 最後 將陣列長度推入堆疊
                                // (推入屬於型別 natural unsigned int) 推入至堆疊。


      IL_001c:  conv.i4//轉換成 int32,並在堆疊上推入 int32

      IL_001d:  clt  //將 value1 推入至堆疊。
                            //value2 推入至堆疊。
                           //value2value1 自堆疊取出;clt 會測試 value2 是否小於 value1
                           //如果 value2 小於 value1,則將 1 推入至堆疊,否則,將 0 推入至堆疊。


      IL_001f:  stloc.3// 將結果放入V3
      IL_0020:  ldloc.3//讀取V3
      IL_0021:  brtrue.s   IL_0008 //取出堆疊 如果V3是 TRUE 跳轉到0008 ,false跳到ref
                                                    //要一子到V2==V1 ,V3才會等於FALSE 跳出迴圈
      IL_0023:  ret
    } // end of method Program::ILforeach

    接下來! 我們將上面的例題做點小變化

    呼叫中的陣列不變 如下

    int[] S = { 0, 1, 2, 3, 4,10 };
                ILforeach(S);

    副程式碼

    static void ILforeach(ICollection<int> values)
            {
                foreach (int E in values)
                {
                    Console.WriteLine(E);
                }
            }

    首先傳入參數為int[] s

    接下來IL 程式碼如下
    .method private hidebysig static void  ILforeach(class [mscorlib]System.Collections.Generic.ICollection`1<int32> values) cil managed
    {
      // Code size       57 (0x39)
      .maxstack  2
      .locals init ([0] int32 E, //V0  foreach中的E
               [1] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> CS$5$0000,//V1
               [2] bool CS$4$0001)//V2
    //V1 中是一個 介面IEnumerator 用於集合上的簡單反覆運算 介面中的方法包含了
    //Current取得集合中目前的項目。
    //MoveNext將列舉值往前推至下集合中的下一個項目。
    //Reset設定列舉值至它的初始位置,這是在集合中第一個元素之前。
    //但是 要操作這方法之前必須先使用 介面IEnumerable中的GetEnumerator 方法
    //取得集合的列舉程式  ,下面0001~0008 將做到這點!

      IL_0000:  nop
      IL_0001:  nop
      IL_0002:  ldarg.0// 將values值推入堆疊

      IL_0003:  callvirt   instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
    //003 是一個GetEnumerator()方法 列舉器 將會列舉values值
    //Callvirt 欄位
    //將物件參考 obj 推入至堆疊。
    //將方法引數 arg1argN 推入至堆疊。
    //將方法引數從 arg1argN 和物件參考 obj 自堆疊取出;以這些引數來執行方法呼叫,且傳
    //輸控制項至方法中繼資料語彙基元所參考的 obj 中的方法。完成時
    //被呼叫端方法會產生傳回值,並傳送至呼叫端。
    //將傳回值推入至堆疊。

      IL_0008:  stloc.1//將列舉結果 存放在V1,此時V_1 = (IEnumerator<int>)values.GetEnumerator()
     
      .try//進入一個try  finally
      {
        IL_0009:  br.s       IL_001b// 跳轉去001b

       
        IL_000b:  ldloc.1//讀取V1推入堆疊中
        IL_000c:  callvirt   instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
                                   //Current取得集合中目前的項目,所以這便可以看成V1.Current方法

        IL_0011:  stloc.0//將陣列中的項目 放入E  也就是存取V0
        IL_0012:  nop
        IL_0013:  ldloc.0//讀取V0
        IL_0014:  call       void [mscorlib]System.Console::WriteLine(int32)//印出V0
        IL_0019:  nop
        IL_001a:  nop

        IL_001b:  ldloc.1// 讀取V1
        IL_001c:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
                                       //001c 將V1 矩陣直 調用了MoveNext()方法,  此方法用於
                                       //如果列舉值成功地前移至下一個項目,則為 true,如果列舉值已超
                                       //過集合的結尾,則為 false
                                       //MoveNext()內的語法長的大概如下
                                       // public bool MoveNext()
                                       // {

                                       //   position++;
                                       //   return (position < V1.Length);
                                       //  }

                                       //呼叫也都會傳回 false,直到呼叫 Reset 為止。
                                        //Reset將會把position=歸回-1
        IL_0021:  stloc.2 //將結果存放到 bool 的 V2
        IL_0022:  ldloc.2 // 讀取V2  看是 true或false
        IL_0023:  brtrue.s   IL_000b// 如果是true 跳到000b,如果不是往下執行
        IL_0025:  leave.s    IL_0037//如果V2是false跳到0037
      }  // end .try
      finally//在try區塊結束時,finally裡面將進行GC回收程序
      {//finally中的工作可以看成是 if(V_1 != null) V_1.Dispose() 
        IL_0027:  ldloc.1//讀取V1放入堆疊
        IL_0028:  ldnull//將 Null 物件參考推入至堆疊
        IL_0029:  ceq//如果 value1 等於 value2,則將 1 推入至堆疊,否則,將 0 推入至堆疊。
        IL_002b:  stloc.2//將結果存入V2
        IL_002c:  ldloc.2//讀取V2
        IL_002d:  brtrue.s   IL_0036//V2是True的話跳到0036,false則繼續執行
        IL_002f:  ldloc.1//如果不是NULL 讀取V1 並執行下面的:Dispose()方法
        IL_0030:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
                          //IDisposable是一個介面Dispose()方法將會釋放V1資源
        IL_0035:  nop
        IL_0036:  endfinally//結束endfinally
      }  // end handler
      IL_0037:  nop
      IL_0038:  ret//返回數值 結束
    } // end of method Program::ILforeach



    上面IL我們看出來foreach中使用了 IEnumerator IEnumerable介面
    只要實現IEnumerable.GetEnumerable方法取得列舉即可在foreach中使用
    再來下面一個範例 我們來實作GetEnumerator() 來看看使否真得可以運行

    首先 我先加入一個CS檔
    裡面有一個類別 叫做OK
    類別中 有實作GetEnumerator()
    程式碼如下

    class OK
        {
            public IEnumerator<int> GetEnumerator() 
            { 
                for (var i = 0; i < 18; i++) 
                  yield return i;  //返回IEnumerator<int> i.GetEnumerator()
            } 
        }


    OK!
    接下來我們來實作他 並SHOW出

    static void Main(string[] args)
            {
                //實體化OK叫WE
                OK WE = new OK();
                rrrtest(WE);//呼叫副程式 帶入WE
             }


    來看一下接下來呼叫的副程式 如下

    static void rrrtest(OK values)
            {
                foreach (int F in values)
                {
                    Console.WriteLine(F);
                }
            }
    S

    再來 看IL程式碼  跟之前沒啥不同 差別在於我們實作了 GetEnumerator()

    .method private hidebysig static void  rrrtest(class _002_foreach_test.nTest.OK values) cil managed
    {
      // Code size       57 (0x39)
      .maxstack  2
      .locals init ([0] int32 F,
               [1] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> CS$5$0000,
               [2] bool CS$4$0001)
      IL_0000:  nop
      IL_0001:  nop
      IL_0002:  ldarg.0//讀取values類別
      IL_0003:  callvirt   instance class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> _002_foreach_test.nTest.OK::GetEnumerator()
                                  //foreach 會去調用GetEnumerator()我們實作的1~18的列舉
      IL_0008:  stloc.1//之後 回存V1           IEnumerator<int> values.GetEnumerator()
    // 之後一樣 是一個 try finally 當try區塊結束時finally將釋放資源
      .try
      {
        IL_0009:  br.s       IL_001b//這裡跟上一題 差不多 後面不再多說了
        IL_000b:  ldloc.1
        IL_000c:  callvirt   instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
        IL_0011:  stloc.0
        IL_0012:  nop
        IL_0013:  ldloc.0
        IL_0014:  call       void [mscorlib]System.Console::WriteLine(int32)
        IL_0019:  nop
        IL_001a:  nop
        IL_001b:  ldloc.1
        IL_001c:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
        IL_0021:  stloc.2
        IL_0022:  ldloc.2
        IL_0023:  brtrue.s   IL_000b
        IL_0025:  leave.s    IL_0037
      }  // end .try
      finally
      {
        IL_0027:  ldloc.1
        IL_0028:  ldnull
        IL_0029:  ceq
        IL_002b:  stloc.2
        IL_002c:  ldloc.2
        IL_002d:  brtrue.s   IL_0036
        IL_002f:  ldloc.1
        IL_0030:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
        IL_0035:  nop
        IL_0036:  endfinally
      }  // end handler
      IL_0037:  nop
      IL_0038:  ret
    } // end of method Program::rrrtest

    沒有留言:

    張貼留言