続・ラムダ式の変数スコープ
実際のところ、値型の場合の実装が( MSIL 上で)どうなっているのか興味が沸いたので調べてみました。
基にしたソースコードは以下。(伊藤さんの記事から拝借しました。 m(_ _)m )
class Foo { static void Main() { Action a; { var i = 10; a = () => Console.WriteLine(++i); } a(); } }
このコードを .NET Reflector にかけたところ、メインメソッドの MSIL はこんな感じになりました。
.method private hidebysig static void Main() cil managed { .entrypoint .maxstack 3 .locals init ( [0] class [System.Core]System.Action action, [1] class Foo/<>c__DisplayClass1 class2) L_0000: nop L_0001: newobj instance void Foo/<>c__DisplayClass1::.ctor() L_0006: stloc.1 L_0007: nop L_0008: ldloc.1 L_0009: ldc.i4.s 10 L_000b: stfld int32 Foo/<>c__DisplayClass1::i L_0010: ldloc.1 L_0011: ldftn instance void Foo/<>c__DisplayClass1::<Main>b__0() L_0017: newobj instance void [System.Core]System.Action::.ctor(object, native int) L_001c: stloc.0 L_001d: nop L_001e: ldloc.0 L_001f: callvirt instance void [System.Core]System.Action::Invoke() L_0024: nop L_0025: ret }
どうやらラムダ式は内部的なクラスとして管理されている様です。
で、ラムダ式内で外側スコープの変数を利用すると、その内部クラスのメンバ変数として再定義されます。
そして、変数のスコープを抜ける直前で、値を内部クラスのメンバ変数にコピーする、と。(使用した変数が参照型だと参照のコピーになるのかな?)
なるほど、これでスコープを抜けても値が保持されると云うわけですね。
ちなみに、 Action デリゲートへの登録は、この内部クラスのメンバメソッドの関数ポインタを引き渡す事で実現していますね。
……ところで、この内部クラスのインスタンスのスコープってどうなっているんだろうか。(↑の例ではちょっと判らなかった。。。)
おまけ
内部クラスの中身は↓こんな感じになっていました。
.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 } .method public hidebysig instance void <Main>b__0() cil managed { .maxstack 3 .locals init ( [0] int32 num) L_0000: ldarg.0 L_0001: dup L_0002: ldfld int32 Foo/<>c__DisplayClass1::i L_0007: ldc.i4.1 L_0008: add L_0009: dup L_000a: stloc.0 L_000b: stfld int32 Foo/<>c__DisplayClass1::i L_0010: ldloc.0 L_0011: call void [mscorlib]System.Console::WriteLine(int32) L_0016: nop L_0017: ret } .field public int32 i }
こちらは、シンプルにラムダ式の実体 (b__0()) と値を保持するためのメンバ変数 (int32 i) があるだけですね。