前章 認識IL程式碼時
在觀察Foreach中的IL我們發現了 Foreach中使用了
IEnumerable 與 IEnumerator
介面 本章將介紹
foreach中如何調用這兩種介面
OK! 首先 我們先來 談談
IEnumerable
可以看到此介面 只提供一個方法
GetEnumerator
此方法會 傳回會逐一查看
集合的列舉程式。
這邊要注意的是IEnumerator .GetEnumerator() 此方法
是回傳IEnumerator 而不是 IEnumerable
所以 IEnumerable只提供了 列舉的功能
其他的事情 將交給IEnumerator
再來 來看
IEnumerator介面
假如 我們在之前的介面 已使用GetEnumerator() 取的列舉後
將列舉值傳到此介面 繼續後續動作
先來看看
IEnumerator 提供了 哪些方法??
bool MoveNext()
如果列舉值成功地前移至下一個項目,則為 true
如果列舉值已超過集合的結尾,則為 false。
意思是說 他會有一個 參數++ >索引.Length
不斷累加 一子到超出索引範圍 才會=false
Reset 方法
設定列舉值至它的初始位置,這是在集合中第一個元素之前。
用於初始化 參數 ,這裡參數 同MoveNext()中累加的參數是同一個
Current 屬性
取得集合中目前的項目。
當MoveNext true時,將集合[參數值] 放入結果中
介紹完Enumeraor Enumerable
簡單來說 我們必須 使用 使用GetEnumerator()
取得矩陣後 再利用Enumerator介面所提供的方法
下面將實作GetEnumerator方法 讓我們能了解foreach中的運作原理
好的 首先 來看一下例題
首先 我們建立一個CS檔案內部實作類別如下
class MyCollection
{
private int[] item; //宣告 私用 整數 陣列叫 item,尚未初始化參數
public MyCollection()
{
this.item = new int[5] {12, 44, 33, 2, 50};//初始化 剛剛宣告的 陣列
}
public MyEnumerator GetEnumerator()//foreach將會呼叫GetEnumerator()
{ //並回傳MyEnumerator類別
return new MyEnumerator(this);//將會實體化MyEnumerator並帶入(item)
}
public class MyEnumerator
{
int nIndex;
MyCollection collection;
public MyEnumerator(MyCollection coll)
{
////當GetEnumerator()被呼叫 同時完成此區塊
collection = coll;
nIndex = -1;
}
public bool MoveNext()
{
nIndex++;
return (nIndex < collection.item.GetLength(0));
}
public int Current
{
get
{ return (collection.item[nIndex]); }
}
}
}
接下來 來看 主程式 呼叫副程式 副程式實作foreach
foreach將會 呼叫GetEnumerator()
程式碼如下
class Program
{
static void Sample_1()
{
Console.WriteLine("*-*-*-*-*-*-範例1 開始。"); Console.ReadLine();
MyCollection col = new MyCollection();//實體化類別 並同事建立item陣列
Console.WriteLine("集合中的值為:");
// 顯示集合中的項目
foreach (int i in col)// 逐步執行當執行到col時 foreach會呼叫GetEnumerator()
{
Console.WriteLine(i);
}
Console.WriteLine("*-*-*-*-*-*-範例1 結束。"); Console.ReadLine();
}
static void Main(string[] args)//主程式
{
Sample_1();//呼叫副程式
}
}
OK! 看完程式碼 想必 第一次 應該很亂吧!
讓我大概解析一下 他的步驟
首先 Program.cs 程式進入開端 main
裡面有一個sample_1();
他將會呼叫同 Program.cs程式檔中的類別
裡面的靜態成員
static void Sample_1()
而這裡面 將會實體化
MyCollection類別
並且初始化類別內部item陣列
當實體化MyCollection col=new MyCollection
類別完後 將執行foreach中col的區塊
此時foreach,將會調用GetEnumerator()公開方法
此時 GetEnumerator()返回的是一個類中類MyEnumerator
GetEnumerator() 裡面將實體化new MyEnumerator(item)
程序便跳到 類中類MyEnumerator 裡面支援一個方法
MyEnumerator(MyCollection coll)
這方法中 將nindex 與collection 做初始化動作
待會這些初始化的參數 將被public bool MoveNext()調用
結束GetEnumerator()內部程序後 也初始化了MyEnumerator中的參數
並將 列舉數值,同樣返回MyEnumerator
這時候 foreach中col 呼叫GetEnumerator的工作已結束
接下來 將執行foreach中的in
此時 foreach 將呼叫bool MoveNext
此方法 返回是一個布林
取決於 剛剛初始化的nIndex是否小於列舉.length
此時nIndex++ 會不斷累加 一子到 陣列終點
MoveNext執行完後 回到foreach 中的in
如果之前MoveNext=false
挑出foreach迴圈 如果是 true
會接下去 執行foreach中的 int i 區塊
foreach將調用public int Current
他將回傳給foreach中的i
來看看Current內部程序 如何回傳
Get
{ return (collection.item[nIndex]); }
很清楚明白的 調用類別Collection中的item[]
之後i再回傳給 console.writeline(i)
結束!!
接下來我們來大概 看一下內部IL程式碼長啥樣?
.method private hidebysig static void Sample_1() cil managed
{
// Code size 118 (0x76)
.maxstack 2
.locals init ([0] class _002_foreach_test.nTest.MyCollection col,
[1] int32 i,
[2] class _002_foreach_test.nTest.MyCollection/MyEnumerator CS$5$0000,
[3] bool CS$4$0001,
[4] class [mscorlib]System.IDisposable CS$0$0002)
//上面宣告了4個V0=MyCollection col 類別
// V1=foreach中的i
// V2=類中類MyEnumerator
// V3=bool 這是MoveNExt
// V4=IDisposable清除資源用
IL_0000: nop
IL_0001: ldstr bytearray (2A 00 2D 00 2A 00 2D 00 2A 00 2D 00 2A 00 2D 00 // *.-.*.-.*.-.*.-.
2A 00 2D 00 2A 00 2D 00 C4 7B 8B 4F 31 00 20 00 // *.-.*.-..{.O1. .
8B 95 CB 59 02 30 ) // ...Y.0
//0001這裡是一個中文與符號的unicode代碼 將被下面console show出
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: call string [mscorlib]System.Console::ReadLine()
IL_0011: pop
IL_0012: newobj instance void _002_foreach_test.nTest.MyCollection::.ctor()//實體化類別
IL_0017: stloc.0 //並存取col
IL_0018: ldstr bytearray (C6 96 08 54 2D 4E 84 76 3C 50 BA 70 1A FF ) // ...T-N.v<P.p..
IL_001d: call void [mscorlib]System.Console::WriteLine(string)
//0018是一個unicode 將被001d SHOW出
IL_0022: nop
IL_0023: nop
IL_0024: ldloc.0//讀取V0推入堆疊
IL_0025: callvirt instance class _002_foreach_test.nTest.MyCollection/MyEnumerator _002_foreach_test.nTest.MyCollection::GetEnumerator()
//0025呼叫GetEnumerator()去實作內部區塊
//而這區塊是指向類中類的MyEnumerator
IL_002a: stloc.2//這邊V2也存一份列舉值
.try
{
IL_002b: br.s IL_003d
IL_002d: ldloc.2 //讀取列舉
IL_002e: callvirt instance int32 _002_foreach_test.nTest.MyCollection/MyEnumerator::get_Current()
IL_0033: stloc.1//將列舉[]放入V1
IL_0034: nop
IL_0035: ldloc.1//讀取V1將被下面0036SHOW出
IL_0036: call void [mscorlib]System.Console::WriteLine(int32)
//此後以此類推如法炮製
IL_003b: nop
IL_003c: nop
IL_003d: ldloc.2// 讀取剛剛存取的列舉值
IL_003e: callvirt instance bool _002_foreach_test.nTest.MyCollection/MyEnumerator::MoveNext()
IL_0043: stloc.3//將MoveNext結果放入V3
IL_0044: ldloc.3//讀取V3
IL_0045: brtrue.s IL_002d//true就跳到002d
IL_0047: leave.s IL_0063//不然就跳出foreach迴圈
} // end .try
finally
{
IL_0049: ldloc.2
IL_004a: isinst [mscorlib]System.IDisposable
IL_004f: stloc.s CS$0$0002
IL_0051: ldloc.s CS$0$0002
IL_0053: ldnull
IL_0054: ceq
IL_0056: stloc.3
IL_0057: ldloc.3
IL_0058: brtrue.s IL_0062
IL_005a: ldloc.s CS$0$0002
IL_005c: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0061: nop
IL_0062: endfinally
} // end handler
IL_0063: nop
IL_0064: ldstr bytearray (2A 00 2D 00 2A 00 2D 00 2A 00 2D 00 2A 00 2D 00 // *.-.*.-.*.-.*.-.
2A 00 2D 00 2A 00 2D 00 C4 7B 8B 4F 31 00 20 00 // *.-.*.-..{.O1. .
50 7D 5F 67 02 30 ) // P}_g.0
IL_0069: call void [mscorlib]System.Console::WriteLine(string)
IL_006e: nop
IL_006f: call string [mscorlib]System.Console::ReadLine()
IL_0074: pop
IL_0075: ret
} // end of method Program::Sample_1
以上是一個 簡單的例子 我們在MYcollection.cs
中沒使用到IEnumerator IEnumerable的
命名空間using System.Collections;
IL程式碼中可以證明!! 並無調用到Collections
接下來我們來看更進階的實做~~
這次實作interface 中的IEnumerator IEunmerable
一樣Main 中 Sample_2_a();呼叫副程式如下
static void Sample_2_a()
{
Console.WriteLine("*-*-*-*-*-*-範例2_a 開始。"); Console.ReadLine();
MyCollection2_a col = new MyCollection2_a();
Console.WriteLine("集合中的值為:");
// 顯示集合中的項目
foreach (int i in col)
{
Console.WriteLine(i);
}
Console.WriteLine("*-*-*-*-*-*-範例2_a 結束。"); Console.ReadLine();
}
來看一下副程式所呼叫的類別CS程式檔中的類別如下
此類使用命名空間 collections
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace _002_foreach_test.nTest
{
// 宣告這個 collection 實做 IEnumerable 介面
class MyCollection2_a : IEnumerable
{
private int[] items;
public MyCollection2_a()
{
this.items = new int[5] { 12, 44, 33, 2, 50 };
}
public MyEnumerator2_a GetEnumrator()
{
return new MyEnumerator2_a(this);//實體化yEnumerator2_a(item)
//並且傳回列舉
}
// 實做 GetEnumerator()
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumrator();//呼叫自己的方法
//等待回傳列舉給IEnumerator
}
// 宣告這個 enumerator 實做 IEnumerator 介面
public class MyEnumerator2_a : IEnumerator
{
private int nIndex;
MyCollection2_a collection;
public MyEnumerator2_a(MyCollection2_a coll)
{
this.collection = coll;//當呼叫GetEnumerator()時
this.nIndex = -1; //此方法中的參數被初始化
}
#region IEnumerator 成員
object IEnumerator.Current
{
get { return (this.collection.items[nIndex]); }
}
bool IEnumerator.MoveNext()
{
this.nIndex++;
return (nIndex < collection.items.GetLength(0));
}
void IEnumerator.Reset()
{
this.nIndex = -1;
}
#endregion
}
}
}
跟上一個範例差不多 不同於實作interface
來看一下IL 證明這次有使用到此命名空間與實作了interface
.method private hidebysig static void Sample_2_a() cil managed
{
// Code size 123 (0x7b)
.maxstack 2
.locals init ([0] class _002_foreach_test.nTest.MyCollection2_a col,
[1] int32 i,
[2] class [mscorlib]System.Collections.IEnumerator CS$5$0000,
[3] bool CS$4$0001,
[4] class [mscorlib]System.IDisposable CS$0$0002)
IL_0000: nop
IL_0001: ldstr bytearray (2A 00 2D 00 2A 00 2D 00 2A 00 2D 00 2A 00 2D 00 // *.-.*.-.*.-.*.-.
2A 00 2D 00 2A 00 2D 00 C4 7B 8B 4F 32 00 5F 00 // *.-.*.-..{.O2._.
61 00 20 00 8B 95 CB 59 02 30 ) // a. ....Y.0
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: call string [mscorlib]System.Console::ReadLine()
IL_0011: pop
IL_0012: newobj instance void _002_foreach_test.nTest.MyCollection2_a::.ctor()
IL_0017: stloc.0
IL_0018: ldstr bytearray (C6 96 08 54 2D 4E 84 76 3C 50 BA 70 1A FF ) // ...T-N.v<P.p..
IL_001d: call void [mscorlib]System.Console::WriteLine(string)
IL_0022: nop
IL_0023: nop
IL_0024: ldloc.0
IL_0025: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.IEnumerable::GetEnumerator()
IL_002a: stloc.2
.try
{
IL_002b: br.s IL_0042
IL_002d: ldloc.2
IL_002e: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
IL_0033: unbox.any [mscorlib]System.Int32
IL_0038: stloc.1
IL_0039: nop
IL_003a: ldloc.1
IL_003b: call void [mscorlib]System.Console::WriteLine(int32)
IL_0040: nop
IL_0041: nop
IL_0042: ldloc.2
IL_0043: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_0048: stloc.3
IL_0049: ldloc.3
IL_004a: brtrue.s IL_002d
IL_004c: leave.s IL_0068
} // end .try
finally
{
IL_004e: ldloc.2
IL_004f: isinst [mscorlib]System.IDisposable
IL_0054: stloc.s CS$0$0002
IL_0056: ldloc.s CS$0$0002
IL_0058: ldnull
IL_0059: ceq
IL_005b: stloc.3
IL_005c: ldloc.3
IL_005d: brtrue.s IL_0067
IL_005f: ldloc.s CS$0$0002
IL_0061: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0066: nop
IL_0067: endfinally
} // end handler
IL_0068: nop
IL_0069: ldstr bytearray (2A 00 2D 00 2A 00 2D 00 2A 00 2D 00 2A 00 2D 00 // *.-.*.-.*.-.*.-.
2A 00 2D 00 2A 00 2D 00 C4 7B 8B 4F 32 00 5F 00 // *.-.*.-..{.O2._.
61 00 20 00 50 7D 5F 67 02 30 ) // a. .P}_g.0
IL_006e: call void [mscorlib]System.Console::WriteLine(string)
IL_0073: nop
IL_0074: call string [mscorlib]System.Console::ReadLine()
IL_0079: pop
IL_007a: ret
} // end of method Program::Sample_2_a
這裡IL內可以看到命名空間System.Collections
裡面有IEnumerator IEnumerable的介面
並且把它時做出來,這樣便能清楚了解它的運作原理
以上不多做介紹 ,因為大同小異
再來同上題,在加一點點小小的變化
IDisposable 並實作一個 Dispose
用意來釋放資源
Dispose將被GC下一次回收時回收
來看例題
一樣Sample_2_b();呼叫副程式
static void Sample_2_b()
{
Console.WriteLine("*-*-*-*-*-*-範例2_b 開始。"); Console.ReadLine();
MyCollection2_b col = new MyCollection2_b();
Console.WriteLine("集合中的值為:");
// 顯示集合中的項目
foreach (int i in col)
{
Console.WriteLine(i);
}
Console.WriteLine("*-*-*-*-*-*-範例2_b 結束。"); Console.ReadLine();
}
再來看
MyCollection2_b.cs程式檔內部的類別與繼承的介面
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace _002_foreach_test.nTest
{
// 宣告這個 collection 實做 IEnumerable 介面
class MyCollection2_b : IEnumerable
{
private int[] items;
public MyCollection2_b()
{
this.items = new int[5] { 12, 44, 33, 2, 50 };
}
public MyEnumerator2_b MY_GetEnumerator()
{
return new MyEnumerator2_b(this);//取得this列舉
//並且實體化MyEnumerator2_b(col)內部參數
}
// 實做 GetEnumerator()
IEnumerator IEnumerable.GetEnumerator()
{ //當執行到了foreach中的col會呼叫此方法
return this.MY_GetEnumerator();//將方法接過去我們自製的方法
//最後將列舉回傳給IEnumeraor()介面
}
// 宣告這個 enumerator 實做 IEnumerator 和 IDisposable 兩個介面
public class MyEnumerator2_b : IEnumerator, IDisposable
{
private int nIndex;
MyCollection2_b collection;
public MyEnumerator2_b(MyCollection2_b coll)
{
this.collection = coll;//拷貝一份列舉給MyEnumerator2_b
this.nIndex = -1;//將nIndex初始化
}
// 自有變數
public int Current
{
get
{ return (collection.items[nIndex]); }
}
// 實做 IEnumerator
#region IEnumerator 成員
public void Reset()
{
this.nIndex = -1;
}
public bool MoveNext()
{
this.nIndex++;
return (this.nIndex < collection.items.GetLength(0));
}
object IEnumerator.Current
{
get { return (Current); }
}
#endregion
//public void Dispose()
//{
// Console.WriteLine("處理中…");
// this.collection = null;
//}
// 實做 IDispos
void IDisposable.Dispose()
{
//標註已不用的資源,待GC下一次回收
Console.WriteLine("處理中…");
this.collection = null;
}
}
}
}
其實這一個範例已經篇題了=V="
IDispos GC 這又是另一門學問了
在來來看最後一個範例
這範例不再實作IEnumerator IEnumerable
直接正常使用foreach 順便看一下IL內部如何規劃
Sample_3() 呼叫副程式 ,內部程式如下
static void Sample_3()
{
Console.WriteLine("*-*-*-*-*-*-範例3 開始。"); Console.ReadLine();
// 宣告一個 Hashtable
Hashtable ziphash = new Hashtable();
// 利用 Add() 加入項目 key,values
ziphash.Add("234", "永和");
ziphash.Add("613", "朴子");
ziphash.Add("210", "北竿");
ziphash.Add("100", "中正區");
ziphash.Add("805", "旗津區");
// 顯示內容
Console.WriteLine("集合中的值為:");
foreach (string zip in ziphash.Keys)//印出
{
Console.WriteLine(zip + "\t" + ziphash[zip]);
}
Console.WriteLine("*-*-*-*-*-*-範例3 結束。"); Console.ReadLine();
}
S
接下來 可以觀察內部IL
將調用GetEnumeraor()方法取得
Hashtable中的集合
IL程式碼因為大同小異 所以就不貼了
完!!