C#のforeachの仕組み(3) IEnumeratorの独自実装コードの続きです。
Niigata.NET 3.0に参加して、forと比較して、foreachをブラックボックスに感じたので、少し整理したい。という話です。
Niigata.NET 3.0に参加してきました
概要
ILにおいて、forとforeachを比較します。
コードとIL
forのコード。
Implement_for
using System;
using System.Collections.Generic;
namespace ConsoleAppFor
{
class Program
{
static void Main()
{
var numbers = new List<int>() { 0, 1, 2 };
for (int i = 0; i < numbers.Count; i++)
{
Console.WriteLine(numbers[i]);
}
}
}
}
forのIL。
IL_for
.method private hidebysig static void Main() cil managed
{
.entrypoint
// コード サイズ 68 (0x44)
.maxstack 3
.locals init (class [System.Collections]System.Collections.Generic.List`1<int32> V_0,
int32 V_1,
bool V_2)
IL_0000: nop
IL_0001: newobj instance void class [System.Collections]System.Collections.Generic.List`1<int32>::.ctor()
IL_0006: dup
IL_0007: ldc.i4.0
IL_0008: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<int32>::Add(!0)
IL_000d: nop
IL_000e: dup
IL_000f: ldc.i4.1
IL_0010: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0015: nop
IL_0016: dup
IL_0017: ldc.i4.2
IL_0018: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<int32>::Add(!0)
IL_001d: nop
IL_001e: stloc.0
IL_001f: ldc.i4.0
IL_0020: stloc.1
IL_0021: br.s IL_0036
IL_0023: nop
IL_0024: ldloc.0
IL_0025: ldloc.1
IL_0026: callvirt instance !0 class [System.Collections]System.Collections.Generic.List`1<int32>::get_Item(int32)
IL_002b: call void [System.Console]System.Console::WriteLine(int32)
IL_0030: nop
IL_0031: nop
IL_0032: ldloc.1
IL_0033: ldc.i4.1
IL_0034: add
IL_0035: stloc.1
IL_0036: ldloc.1
IL_0037: ldloc.0
IL_0038: callvirt instance int32 class [System.Collections]System.Collections.Generic.List`1<int32>::get_Count()
IL_003d: clt
IL_003f: stloc.2
IL_0040: ldloc.2
IL_0041: brtrue.s IL_0023
IL_0043: ret
} // end of method Program::Main
foreachのコード。
Implement_foreach
using System;
using System.Collections.Generic;
namespace ConsoleAppForeach
{
class Program
{
static void Main()
{
var numbers = new List<int>() { 0, 1, 2 };
foreach(var n in numbers)
{
Console.WriteLine(n);
}
}
}
}
foreachのIL。
IL_foreach
.method private hidebysig static void Main() cil managed
{
.entrypoint
// コード サイズ 85 (0x55)
.maxstack 3
.locals init (class [System.Collections]System.Collections.Generic.List`1<int32> V_0,
valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32> V_1,
int32 V_2)
IL_0000: nop
IL_0001: newobj instance void class [System.Collections]System.Collections.Generic.List`1<int32>::.ctor()
IL_0006: dup
IL_0007: ldc.i4.0
IL_0008: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<int32>::Add(!0)
IL_000d: nop
IL_000e: dup
IL_000f: ldc.i4.1
IL_0010: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0015: nop
IL_0016: dup
IL_0017: ldc.i4.2
IL_0018: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<int32>::Add(!0)
IL_001d: nop
IL_001e: stloc.0
IL_001f: nop
IL_0020: ldloc.0
IL_0021: callvirt instance valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<!0> class [System.Collections]System.Collections.Generic.List`1<int32>::GetEnumerator()
IL_0026: stloc.1
.try
{
IL_0027: br.s IL_003a
IL_0029: ldloca.s V_1
IL_002b: call instance !0 valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
IL_0030: stloc.2
IL_0031: nop
IL_0032: ldloc.2
IL_0033: call void [System.Console]System.Console::WriteLine(int32)
IL_0038: nop
IL_0039: nop
IL_003a: ldloca.s V_1
IL_003c: call instance bool valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
IL_0041: brtrue.s IL_0029
IL_0043: leave.s IL_0054
} // end .try
finally
{
IL_0045: ldloca.s V_1
IL_0047: constrained. valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>
IL_004d: callvirt instance void [System.Runtime]System.IDisposable::Dispose()
IL_0052: nop
IL_0053: endfinally
} // end handler
IL_0054: ret
} // end of method Program::Main
ILの相違点
diff_IL
@@ -1,11 +1,11 @@
.method private hidebysig static void Main() cil managed
{
.entrypoint
- // コード サイズ 68 (0x44)
+ // コード サイズ 85 (0x55)
.maxstack 3
.locals init (class [System.Collections]System.Collections.Generic.List`1<int32> V_0,
- int32 V_1,
- bool V_2)
+ valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32> V_1,
+ int32 V_2)
IL_0000: nop
IL_0001: newobj instance void class [System.Collections]System.Collections.Generic.List`1<int32>::.ctor()
IL_0006: dup
@@ -21,26 +21,33 @@
IL_0018: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<int32>::Add(!0)
IL_001d: nop
IL_001e: stloc.0
- IL_001f: ldc.i4.0
- IL_0020: stloc.1
- IL_0021: br.s IL_0036
- IL_0023: nop
- IL_0024: ldloc.0
- IL_0025: ldloc.1
- IL_0026: callvirt instance !0 class [System.Collections]System.Collections.Generic.List`1<int32>::get_Item(int32)
- IL_002b: call void [System.Console]System.Console::WriteLine(int32)
- IL_0030: nop
- IL_0031: nop
- IL_0032: ldloc.1
- IL_0033: ldc.i4.1
- IL_0034: add
- IL_0035: stloc.1
- IL_0036: ldloc.1
- IL_0037: ldloc.0
- IL_0038: callvirt instance int32 class [System.Collections]System.Collections.Generic.List`1<int32>::get_Count()
- IL_003d: clt
- IL_003f: stloc.2
- IL_0040: ldloc.2
- IL_0041: brtrue.s IL_0023
- IL_0043: ret
+ IL_001f: nop
+ IL_0020: ldloc.0
+ IL_0021: callvirt instance valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<!0> class [System.Collections]System.Collections.Generic.List`1<int32>::GetEnumerator()
+ IL_0026: stloc.1
+ .try
+ {
+ IL_0027: br.s IL_003a
+ IL_0029: ldloca.s V_1
+ IL_002b: call instance !0 valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
+ IL_0030: stloc.2
+ IL_0031: nop
+ IL_0032: ldloc.2
+ IL_0033: call void [System.Console]System.Console::WriteLine(int32)
+ IL_0038: nop
+ IL_0039: nop
+ IL_003a: ldloca.s V_1
+ IL_003c: call instance bool valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
+ IL_0041: brtrue.s IL_0029
+ IL_0043: leave.s IL_0054
+ } // end .try
+ finally
+ {
+ IL_0045: ldloca.s V_1
+ IL_0047: constrained. valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>
+ IL_004d: callvirt instance void [System.Runtime]System.IDisposable::Dispose()
+ IL_0052: nop
+ IL_0053: endfinally
+ } // end handler
+ IL_0054: ret
} // end of method Program::Main
- for
- int32 V_1をインデックスとしてインクリメントする。
- V_1 < list.get_Count()の結果を、bool V_2へ設定する(真の場合は1,偽の場合は0)
- V2が0(=True)になるまで繰り返し。
- foreach
- Enumerator V_1をイテレータとする。
- Enumerator.MoveNext()の結果を、int32 V_2へ設定する。
- MoveNext()が偽を返すまで繰り返し。
まとめ
実装した通りでした。
計4回にわたりましたが、現時点でforeachの調査結果は、以上の通りです。
ありがとうございました。