[C#] クロージャー怖い (その1)

2012年9月16日

 以下のコードは、WPF系のフレームワーク(WPF/Windows Phone/Win8) を想定しています。Winforms や Consoleでは動作しないので注意。

 C# や VB.net で 無名関数(ラムダ式?) を作るとき、無名関数ではなく、無名関数を定義した側の変数を参照することができます。クロージャーと呼ぶらしいです。例えば以下。

private void button1_Click(...)
{
    int i = 1;

    Dispatcher.BeginInvoke
    (
        () =>
        {
            MessageBox.Show(i.ToString());
        }
    );
}

結果は 1 が表示されます。無名関数の部分は緑マーカーの部分ですが、赤マーカー部分の設定が表示されています。要するに(無名)関数の本来のスコープから外れたところの値を、引数で渡すとかすることなく、参照できてしまうわけです。

ところでこのクロージャー。便利ですが、いろいろ罠があります。

private void button1_Click(...)
{
    for (int i=0; i<5; i+=1)
    {
        Dispatcher.BeginInvoke
        (
            () =>
            {
                MessageBox.Show(i.ToString());
            }
        );
    }
}

上記のコードの場合、for で 5回ループさせて、そのループ変数をさせようとしたつもりなので、結果は 0→1→2→3→4 と表示されそうですが、そうはなりません。 5回表示されますが、表示される数字は全て 5 です。

なんでさ。

また、上記のコードの for が抜けてから i = 10; を入れると、5 回の表示すべてが 10 となってしまいます。

private void button1_Click(...)
{
    int i; 

    for (i = 0; i < 5; i += 1)
    {
        Dispatcher.BeginInvoke(
            () =>
            {
                MessageBox.Show(i.ToString());
            }
        );
    }

    i = 10;
}

 for の外で代入した数値がなぜ!!

 そもそも、当初のもくろみとしては 0→1→2→3→4 と表示したかったはずです。クロージャーを使うと実現できないのでしょうか。

 できます。

 以下のように書けばいいです。

private void button1_Click(...)
{
    for (int i = 0; i < 5; i += 1)
    {
        int a = i;

        Dispatcher.BeginInvoke(
            () =>
            {
                MessageBox.Show(a.ToString());
            }
        );
    }
}

 この結果からみると、要するに、クロージャーに渡した変数がスコープ外になった時の値が表示される。ということがいえそうです。(*1)

(*1)
 最後のコードで、for の ループ変数 i と、for ループ内の int a のスコープは違います。
 ループ変数 i は次回ループでもそのインスタンスが継続して使用されますが、a のほうはループごとに新しいインスタンスが生成されている点に注意です。






タグ:
カテゴリー: C#, Program

Follow comments via the RSS Feed | Leave a comment | Trackback URL

コメントを投稿する

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


«   »
 
Powered by Wordpress and MySQL. Theme by Shlomi Noach, openark.org