在文章開始前 首先必須逐步讓讀者清楚明白
什麼是IL?? MSIL?? 首先MSIL是簡稱
全名是( Microsoft Intermediate Language )
中文是"中間語言"! 那一定會問什麼是中間語言?
MSDN解釋如下
編譯器會將您的原始程式碼轉譯成 Microsoft Intermediate Language (MSIL),它是可以有效率地轉換為機器碼而與 CPU 無關的指令集。MSIL 包括可用來載入、儲存、初始化和呼叫物件上方法的指令,以及用於數學和邏輯作業、控制流程、直接記憶體存取、例外處理和其他作業的指令。在程式碼可以執行之前,必須將 MSIL 轉換為 CPU 特定程式碼,而此轉換通常是藉 Just-In-Time (JIT) 編譯器進行。
本章不做架構上的深層探討,我們將直接進入主題
首先 先來一個簡單的範例當開端
我們建立一個方法(帶入參數i與j)
我們建立一個方法(帶入參數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源程序中定義的局部變量s,t,r一一對應,我們大概也能猜到這一句區域變數初始化的工作,但是奇怪的是怎麼會有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 推入至堆疊。
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)
//
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"//
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值
//
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[])推入至堆疊。
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//
IL_001d: clt //將 value1 推入至堆疊。
//
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
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 欄位
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()方法, 此方法用於
//
// {
// return (position < V1.Length);
// }
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//
IL_0029: ceq//
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
沒有留言:
張貼留言