続続・ラムダ式の変数スコープ
前の記事でかるあさんに指摘されてコメントした件について、改めて MSIL をみてみました。
まずは検証に使ったコードを書いておきます。(簡単なコードだったので、デザイナ部分を含めて全部直書きしました)
using System; using System.Drawing; using System.Windows.Forms; class Foo : Form { private Button button1; private Button button2; public Foo() { InitializeComponent(); var i = 0; button1.Click += (sender, e) => MessageBox.Show((++i).ToString()); button2.Click += (sender, e) => MessageBox.Show((++i).ToString()); } private void InitializeComponent() { button1 = new Button() { Location = new Point(5, 5), Size = new Size(80, 21), Text = "一個目" }; button2 = new Button() { Location = new Point(5, 30), Size = new Size(80, 21), Text = "二個目" }; ClientSize = new Size(120, 65); Controls.Add(button1); Controls.Add(button2); } static void Main() { Application.Run(new Foo()); } }
これを .NET Reflector にかけて Foo クラスのコンストラクタ部分の MSIL を取り出します。
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { .maxstack 4 .locals init ( [0] class Foo/<>c__DisplayClass2 class2) L_0000: ldarg.0 L_0001: call instance void [System.Windows.Forms]System.Windows.Forms.Form::.ctor() L_0006: nop L_0007: newobj instance void Foo/<>c__DisplayClass2::.ctor() L_000c: stloc.0 L_000d: nop L_000e: ldarg.0 L_000f: call instance void Foo::InitializeComponent() L_0014: nop L_0015: ldloc.0 L_0016: ldc.i4.0 L_0017: stfld int32 Foo/<>c__DisplayClass2::i L_001c: ldarg.0 L_001d: ldfld class [System.Windows.Forms]System.Windows.Forms.Button Foo::button1 L_0022: ldloc.0 L_0023: ldftn instance void Foo/<>c__DisplayClass2::<.ctor>b__0(object, class [mscorlib]System.EventArgs) L_0029: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int) L_002e: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::add_Click(class [mscorlib]System.EventHandler) L_0033: nop L_0034: ldarg.0 L_0035: ldfld class [System.Windows.Forms]System.Windows.Forms.Button Foo::button2 L_003a: ldloc.0 L_003b: ldftn instance void Foo/<>c__DisplayClass2::<.ctor>b__1(object, class [mscorlib]System.EventArgs) L_0041: newobj instance void [mscorlib]System.EventHandler::.ctor(object, native int) L_0046: callvirt instance void [System.Windows.Forms]System.Windows.Forms.Control::add_Click(class [mscorlib]System.EventHandler) L_004b: nop L_004c: nop L_004d: ret }
内部的に生成されるクラスは c__DisplayClass2 のひとつだけで、そのクラスに二つのラムダ式が作られているのが判ります。
ついでに c__DisplayClass2 の方はこんな感じです。
.class auto ansi sealed nested private beforefieldinit <>c__DisplayClass2 extends [mscorlib]System.Object { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: call instance void [mscorlib]System.Object::.ctor() L_0006: ret } .method public hidebysig instance void <.ctor>b__0(object sender, class [mscorlib]System.EventArgs e) cil managed { .maxstack 3 .locals init ( [0] int32 num) L_0000: ldarg.0 L_0001: dup L_0002: ldfld int32 Foo/<>c__DisplayClass2::i L_0007: ldc.i4.1 L_0008: add L_0009: dup L_000a: stloc.0 L_000b: stfld int32 Foo/<>c__DisplayClass2::i L_0010: ldloc.0 L_0011: stloc.0 L_0012: ldloca.s num L_0014: call instance string [mscorlib]System.Int32::ToString() L_0019: call valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult [System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string) L_001e: pop L_001f: ret } .method public hidebysig instance void <.ctor>b__1(object sender, class [mscorlib]System.EventArgs e) cil managed { .maxstack 3 .locals init ( [0] int32 num) L_0000: ldarg.0 L_0001: dup L_0002: ldfld int32 Foo/<>c__DisplayClass2::i L_0007: ldc.i4.1 L_0008: add L_0009: dup L_000a: stloc.0 L_000b: stfld int32 Foo/<>c__DisplayClass2::i L_0010: ldloc.0 L_0011: stloc.0 L_0012: ldloca.s num L_0014: call instance string [mscorlib]System.Int32::ToString() L_0019: call valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult [System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string) L_001e: pop L_001f: ret } .field public int32 i }
b__0() と b__1() でメンバ変数 i を共有しているのが判ります。
内部クラスの個数
先の記事にコメントした際、「同じスコープ内で定義されたラムダ式は、ひとつの内部クラスにまとめられる」と書いたのですが、ちょっと違いました。
例えば、↓こんな事をやると内部クラスは 2 つ生成されます。
Func<int> f; var i = 4; { var j = 5; f = () => i + j; j += 10; } i += 3; Console.WriteLine(f());
ラムダ式に取り込む変数 (i, j) のスコープをずらしてみました。
この場合の MSIL は↓こんな感じになります。
.method private hidebysig static void Main() cil managed { .entrypoint .maxstack 3 .locals init ( [0] class [System.Core]System.Func`1<int32> func, [1] class Foo/<>c__DisplayClass3 class2, [2] class Foo/<>c__DisplayClass1 class3) L_0000: newobj instance void Foo/<>c__DisplayClass1::.ctor() L_0005: stloc.2 L_0006: nop L_0007: ldloc.2 L_0008: ldc.i4.4 L_0009: stfld int32 Foo/<>c__DisplayClass1::i L_000e: newobj instance void Foo/<>c__DisplayClass3::.ctor() L_0013: stloc.1 L_0014: ldloc.1 L_0015: ldloc.2 L_0016: stfld class Foo/<>c__DisplayClass1 Foo/<>c__DisplayClass3::CS$<>8__locals2 L_001b: nop L_001c: ldloc.1 L_001d: ldc.i4.5 L_001e: stfld int32 Foo/<>c__DisplayClass3::j L_0023: ldloc.1 L_0024: ldftn instance int32 Foo/<>c__DisplayClass3::<Main>b__0() L_002a: newobj instance void [System.Core]System.Func`1<int32>::.ctor(object, native int) L_002f: stloc.0 L_0030: ldloc.1 L_0031: dup L_0032: ldfld int32 Foo/<>c__DisplayClass3::j L_0037: ldc.i4.s 10 L_0039: add L_003a: stfld int32 Foo/<>c__DisplayClass3::j L_003f: nop L_0040: ldloc.2 L_0041: dup L_0042: ldfld int32 Foo/<>c__DisplayClass1::i L_0047: ldc.i4.3 L_0048: add L_0049: stfld int32 Foo/<>c__DisplayClass1::i L_004e: ldloc.0 L_004f: callvirt instance !0 [System.Core]System.Func`1<int32>::Invoke() L_0054: call void [mscorlib]System.Console::WriteLine(int32) L_0059: nop L_005a: nop L_005b: ret }
i と j が別々の内部クラス( c__DisplayClass3 と c__DisplayClass1 )で管理されているのが判ります。
つまり、外側スコープを取り込んだラムダ式は、「取り込む変数のスコープごとに内部クラスにまとめられる」様です。
ちなみに、それぞれのクラスの MSIL も挙げておきます。
c__DisplayClass3
.class auto ansi sealed nested private beforefieldinit <>c__DisplayClass3 extends [mscorlib]System.Object { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: call instance void [mscorlib]System.Object::.ctor() L_0006: ret } .method public hidebysig instance int32 <Main>b__0() cil managed { .maxstack 2 .locals init ( [0] int32 num) L_0000: ldarg.0 L_0001: ldfld class Foo/<>c__DisplayClass1 Foo/<>c__DisplayClass3::CS$<>8__locals2 L_0006: ldfld int32 Foo/<>c__DisplayClass1::i L_000b: ldarg.0 L_000c: ldfld int32 Foo/<>c__DisplayClass3::j L_0011: add L_0012: stloc.0 L_0013: br.s L_0015 L_0015: ldloc.0 L_0016: ret } .field public class Foo/<>c__DisplayClass1 CS$<>8__locals2 .field public int32 j }
c__DisplayClass1
.class auto ansi sealed nested private beforefieldinit <>c__DisplayClass1 extends [mscorlib]System.Object { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: call instance void [mscorlib]System.Object::.ctor() L_0006: ret } .field public int32 i }
i の方は、変数管理しかしていないですね。 ^^;