normalian blog

Let's talk about Microsoft Azure, ASP.NET and Java!

続続 ラムダ式の上位スコープ保存

以前書いた記事、「続 ラムダ式の上位スコープ保存」について、 id:karuakun に突っ込みを頂いたので今更返答をば。
突っ込み頂いた内容について考察してみたりします。まずは頂いたコード例は以下。

IList<Action> list3 = new List<Action>();
for (int i = 0; i < 3; i++)
{
    list3.Add(((Func<int, Action>)(k => () => Console.WriteLine(k)))(i));
}
foreach (var func in list3)
{
    func();
}
//0
//1
//2

こちらのコードについて考えてみようかと。いきなり全部を理解しようとすると追えなくなるので一個一個確認する。

k => () => Console.WriteLine(k)

まずは上記のコードですが「int型の引数に対してAction型のラムダ式を返す」ラムダ式、つまりFuncを定義しています。
次に、list3.Addの中身全体

((Func<int, Action>)(k => () => Console.WriteLine(k)))(i)

こいつについてですが、やってる事はFuncラムダ式にint型引数iを渡して関数実行し、Action型のラムダ式を返しています。

で、なんでこれを行うとうまくfor文内の値がうまくラムダ式のスコープに保存されるのか。理由自体は単純で、ラムダ式が参照する変数を変えてるからです。具体的には以下。

  • 以前書いた記事 ⇒ for文の変数iを直参照
  • id:karuakunの例 ⇒ Funcラムダ式の変数kを参照

という形で、別の変数を参照しています。

さらに、ラムダ式のスコープ保存ですが、これって値の保存じゃなくて、変数の参照を保存してるだけなんですね。なので、以下例を見ると・・・

var num = 0;
Func<int> f = () => num;
num += 10;
Console.WriteLine(f());
// 10

結果は10となります。単純に上位スコープの"値"を保存しているので有れば、Console.WriteLineは0になるはずです。しかし、結果を見てわかるとおりでConsole.WriteLineの実行結果は10です。したがって、「ラムダ式の上位スコープ保存」とは上位スコープの"参照先"を保存しているもので、上位スコープの"値"を保存しているわけではないという結論です。