透過IL看C# (3)——foreach語句

來源:互聯網
上載者:User
透過IL看C# (3)
foreach語句

原文地址:http://www.cnblogs.com/AndersLiu/archive/2009/02/04/csharp-via-il-foreach.html

原創:Anders Liu

摘要:foreach語句是C#中一種重要的迴圈語句,用於遍曆一個數組或對象集合中的每一個元素。這一篇文章介紹了在面對數組、IEnumerable介面和自訂類型時,編譯器為foreach語句產生的IL代碼。

foreach語句是C#中一種重要的迴圈語句,用於遍曆一個數組或對象集合中的每一個元素。foreach語句的基本形式如下:

代碼1 - foreach語句的基本形式

<br />foreach([變數] in [集合])<br /> [語句或語句塊]<br />

foreach語句的作用就是,對於代碼1中的[集合],每次迴圈都取出一個元素放在[變數]中,然後執行一次[語句或語句塊]。注意,在[語句或語句塊]中,[變數]是唯讀。也就是說,只能訪問[變數]的值,而不能為其賦值。

在foreach語句中使用數組

數組是最簡單的集合類型,也最常用在foreach語句中。代碼2給出了一個簡單的foreach迴圈。

代碼2 - 使用數組的foreach迴圈

<br />static void Test(int[] values)<br />{<br /> foreach(int i in values)<br /> Console.WriteLine(i);<br />}<br />

代碼3給出了使用ILDASM工具得到的編譯器為上述代碼產生的IL。其中我用C#代碼給出了注釋,便於查看。

代碼3 - 代碼2對應的IL

<br />.method private hidebysig static void Test(int32[] values) cil managed<br />{<br /> // 代碼大小 34 (0x22)<br /> .maxstack 2<br /> .locals init (int32 V_0, // i<br /> int32[] V_1, // values的副本<br /> int32 V_2, // 中間變數,用做數組下標(迴圈變數)<br /> bool V_3) // 中間變數,用於判斷迴圈終止條件<br /> IL_0000: nop<br /> IL_0001: nop</p><p> // V_1 = values<br /> IL_0002: ldarg.0<br /> IL_0003: stloc.1</p><p> // V_2 = 0<br /> IL_0004: ldc.i4.0<br /> IL_0005: stloc.2</p><p> // goto IL_0017<br /> IL_0006: br.s IL_0017</p><p> // V_0 = V_1[V_2]<br /> IL_0008: ldloc.1<br /> IL_0009: ldloc.2<br /> IL_000a: ldelem.i4<br /> IL_000b: stloc.0</p><p> // Console.WriteLine(V_0)<br /> IL_000c: ldloc.0<br /> IL_000d: call void [mscorlib]System.Console::WriteLine(int32)<br /> IL_0012: nop</p><p> // V_2++<br /> IL_0013: ldloc.2<br /> IL_0014: ldc.i4.1<br /> IL_0015: add<br /> IL_0016: stloc.2</p><p> // V_3 = V_2 < V_1.Length<br /> IL_0017: ldloc.2<br /> IL_0018: ldloc.1<br /> IL_0019: ldlen<br /> IL_001a: conv.i4<br /> IL_001b: clt<br /> IL_001d: stloc.3</p><p> // if(V_3) goto IL_0008<br /> IL_001e: ldloc.3<br /> IL_001f: brtrue.s IL_0008</p><p> IL_0021: ret<br />} // end of method Program::Test<br />

代碼3中出現的中間變數V_2(在代碼2中沒有對應的變數)和IL_0013處的“V_2++”暴露了這段代碼的結構特徵——和for語句是一樣的。

由此,可以得到第一個結論——對於使用數組的foreach語句來說,編譯器會將其翻譯為和for語句類似的IL代碼。但要注意,兩者之間還是有區別的,前文已經提到過,在foreach語句中數組元素是唯讀。

在foreach語句中使用一般集合(ICollection)

接下來,我們嘗試在foreach迴圈中使用一般性的集合,這裡用到的是一個實現了ICollection<int>介面的參數。請參見代碼4。

代碼4 - 使用ICollection的foreach迴圈

<br />static void Test(ICollection<int> values)<br />{<br /> foreach(int i in values)<br /> Console.WriteLine(i);<br />}<br />

代碼5是代碼4對應的IL代碼,同樣給出了C#語句的注釋。

代碼5 - 代碼4對應的IL

<br />.method private hidebysig static void Test(class [mscorlib]System.Collections.Generic.ICollection`1<int32> values) cil managed<br />{<br /> // 代碼大小 55 (0x37)<br /> .maxstack 2<br /> .locals init (int32 V_0, // i<br /> class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> V_1,<br /> bool V_2)<br /> IL_0000: nop<br /> IL_0001: nop</p><p> // V_1 = (IEnumerator<int>)values.GetEnumerator()<br /> IL_0002: ldarg.0<br /> IL_0003: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()<br /> IL_0008: stloc.1</p><p> .try<br /> {<br /> // goto IL_0019<br /> IL_0009: br.s IL_0019</p><p> // V_0 = V_1.Current<br /> IL_000b: ldloc.1<br /> IL_000c: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()<br /> IL_0011: stloc.0</p><p> // Console.WriteLine(V_0)<br /> IL_0012: ldloc.0<br /> IL_0013: call void [mscorlib]System.Console::WriteLine(int32)<br /> IL_0018: nop</p><p> // V_2 = V_1.MoveNext()<br /> IL_0019: ldloc.1<br /> IL_001a: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()<br /> IL_001f: stloc.2</p><p> // if(V_2) goto IL_000b else goto IL_0035 //(IL_0035===ret)<br /> IL_0020: ldloc.2<br /> IL_0021: brtrue.s IL_000b<br /> IL_0023: leave.s IL_0035<br /> } // end .try<br /> finally<br /> {<br /> // if(V_1 != null) V_1.Dispose()<br /> IL_0025: ldloc.1<br /> IL_0026: ldnull<br /> IL_0027: ceq<br /> IL_0029: stloc.2<br /> IL_002a: ldloc.2<br /> IL_002b: brtrue.s IL_0034<br /> IL_002d: ldloc.1<br /> IL_002e: callvirt instance void [mscorlib]System.IDisposable::Dispose()<br /> IL_0033: nop<br /> IL_0034: endfinally<br /> } // end handler<br /> IL_0035: nop<br /> IL_0036: ret<br />} // end of method Program::Test<br />

可以看到,在方法的最開始,即調用ICollection類型的GetEnumerable()方法得到了一個列舉程式(IEnumerator)。

從IL_0009到IL_0023(.try塊內的部分)實際上是一個while迴圈結構。只不過在用C#寫程式時,是在while語句頂部進行條件判斷的,而對應的IL則是在整個迴圈的底部進行判斷。

由此,我們又得到了第二個結論——對於一般性的集合,foreach語句會首先會將集合轉變為IEnumerable介面(ICollection介面繼承自IEnumerable),然後取得IEnumerator對象,之後通過while迴圈進行遍曆。

同時,也糾正了前文的一個說法,那就是,foreach語句不僅適用於“集合”,只要是“可枚舉”的對象(實現了IEnumerable介面),都可以使用foreach進行遍曆。

在foreach語句中使用自訂類型

讓我們再進一步,如果我們自己寫一個類型,能否將其對象作為“集合”用foreach進行遍曆呢?

通過上面的結論2可以推測,只要我們自己寫的類型實現了IEnumerable介面(包括IEnumerable泛型介面),就應該可以。

答案也是肯定的。

不過,事實是,即便不實現IEnumerable介面,只要提供了簽名是public IEnumerator GetEnumerator()的方法,一個自訂類型的對象依然可以通過foreach進行遍曆。例如代碼6給出的類型。

代碼6 - 可以在foreach語句中使用的自訂類型

<br />public class MyEnumerator<br />{<br /> public IEnumerator<int> GetEnumerator()<br /> {<br /> for (var i = 0; i < 10; i++)<br /> yield return i;<br /> }<br />}<br />

需要注意的是,這個GetEnumerator方法,必須是public並且不帶參數,其傳回值必須是IEnumerator或IEnumerator<T>。這個限制,其實和實現IEnumerable介面是一樣的。所以,大家只要瞭解到這一特性就可以了,如果真的需要使用foreach語句遍曆自己的類型,還是實現IEnumerable介面為上。

下面是一個使用自訂類型的foreach語句的樣本。編譯器為其產生的IL與代碼5類似,這裡就不再羅列了,讀者可以自己用ILDasm看一看。

代碼7 - 使用自訂類型的foreach語句

<br />static void Test(MyEnumerator values)<br />{<br /> foreach(int i in values)<br /> Console.WriteLine(i);<br />}<br />小結

本文介紹了在foreach語句中使用數組、一般性集合和自訂類型的情形。

在使用數組時,foreach和for是等價的(但foreach只能進行唯讀遍曆),所以我們無需擔心擷取和使用列舉程式時的開銷。

在使用一般集合和自訂類型時,實際上是通過調用GetEnumerator方法擷取了列舉程式之後,通過while迴圈進行遍曆的。雖然一個自訂類型只要實現了具有特定簽名的GetEnumerator方法,就可以結合foreach語句使用,但還是建議在開發這樣的類型時實現IEnumerable介面。

返回目錄:透過IL看C#

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.