はじめに
C# ラムダ式で(ラムダ式の外側の)ローカル変数を使用するとどうなるのか気になり、やってみました。
本記事は実験的な内容であり、この実装方法を推奨するものではありません。実用的な内容でもありません。
(どちらかというと、うっかり書かないように気を付けましょうという内容です。)
実験1
下記のプログラムを実行し、ボタンをクリックするとコンソールに何が表示されるでしょうか?
using System;
using System.Drawing;
using System.Windows.Forms;
class LambdaSample : Form
{
static readonly int N = 3;
Button[] btn;
LambdaSample()
{
btn = new Button[N];
for(int i=0;i<N;i++)
{
btn[i] = new Button(){
Location = new Point(0, 30*i),
Size = new Size(50, 25),
Text = i.ToString(),
};
Controls.Add(btn[i]);
btn[i].Click += (s,e)=>{Console.WriteLine(i);}; // ...(※)
}
}
[STAThread]
static void Main(string[] args)
{
Application.Run(new LambdaSample());
}
}
結果
いずれのボタンを押しても
3
が出力されます。
変数i
が、for文を抜けても生き残っているようです。
(※のラムダ式(s,e)=>{...};
からi
が参照されているのでスコープを抜けても生き残っているよう。)
逆アセンブル結果
ILDASMで見てみると、<>c__DisplayClass3
という謎のクラスが生成され、i
がフィールドとして存在しています。
ちなみに、(※)の部分のコードからi
を削除すると下記のようになります。
実験2
btn[i].Click += (s,e)=>{Console.WriteLine(i);}; // ...(※)
の部分を
btn[i].Click += (s,e)=>{Console.WriteLine(i); i++;}; // ...(※)
にしてみると、
いずれかのボタンを押すたびに
3
4
5
と出力されます。つまり、i
が生き残っていることが確認できます。