続続・ラムダ式の変数スコープ
前の記事でかるあさんに指摘されてコメントした件について、改めて 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 の方は、変数管理しかしていないですね。 ^^;