前回、「クロージャーに渡した変数がスコープ外になった時の値が表示される」とか書いたわけですが、それは本当なのか?
たとえば以下。
private void button1_Click(...)
{
int i=1;
Action f = () =>
{
MessageBox.Show(i.ToString());
};
// button1_Click内で実行
f.Invoke();
// button1_Click内の後で実行
Dispatcher.BeginInvoke(f);
// i を変更
i = 10;
}
これは、f.Invoke(); では 1 がちゃんと表示されます。Dispatcher.BeginInvoke(f); のほうは予定通り 10 で表示されます。
ではこれでは?
private Action f; private void button1_Click(...) { int i=0; f = () => { i += 1; MessageBox.Show(i.ToString()); }; } private void button2_Click(...) { f.Invoke(); } private void button3_Click(...) { Dispatcher.BeginInvoke(f); }
最初の1回のみ button1_Click() を実行し、button2_Click() , button3_Click() を適当に実行します。するとどうなるか?インクリメントしていってますよね?
要するに、キャプチャされた変数 (今回の場合は i ) は別に親のスコープから外れたときに固定化されるのではなく、
・変数の実体 (値) を参照化 (ボクシング処理) し、
・その参照を無名関数 (上記の場合、f) が所持する。
ということをしていそうです。
だからもっとひどいことをすればこんなこともできる。
private Action f1;
private Action f2;
private void button1_Click(...)
{
int i=0;
f1 = () =>
{
i += 1;
MessageBox.Show(i.ToString());
};
f2= () =>
{
i += 100;
MessageBox.Show(i.ToString());
};
}
private void button2_Click(...)
{
f1.Invoke();
}
private void button3_Click(...)
{
Dispatcher.BeginInvoke(f2);
}
関数を変えても、参照した変数 i は同じなので、f1 と f2 で i は共有されています。だから、同じ実体 (=i) に対して、f1 を実行すれば +1 , f2 を実行すれば +100 されていきます。