web-dev-qa-db-ja.com

C#5.0でキャプチャされたクロージャ(ループ変数)

これは、C#5.0では正常に機能します(期待どおりの意味です)。

var actions = new List<Action>();
foreach (var i in Enumerable.Range(0, 10))
{
    actions.Add(() => Console.WriteLine(i));
}
foreach (var act in actions) act();

0から9を出力します。ただし、これは10を10回表示します。

var actions = new List<Action>();
for (var i = 0; i < 10; i++)
{
    actions.Add(() => Console.WriteLine(i));
}
foreach (var act in actions) act();

質問:これは、5.0より前のC#バージョンで発生した問題でした。そのため、クロージャーにループローカルプレースホルダーを使用する必要がありましたが、C#5.0では「foreach」ループで修正されました。しかし、「for」ループではありません!

この背後にある理由は何ですか(forループの問題も修正されていません)?

47

この背後にある理由は何ですか?

「なぜforループでも変更されなかったのですか?」という意味だと思います。

答えは、forループの場合、既存の動作は完全に理にかなっているということです。 forループを次のように分割した場合:

  • イニシャライザ
  • 状態
  • イテレータ

...ループは大まかに:

{
    initializer;
    while (condition)
    {
        body;
        iterator;
    }
}

(もちろん、iteratorcontinue;ステートメントの最後でも実行されることを除いて。)

初期化部分論理的には1回だけ発生するため、「変数のインスタンス化」が1つしかないことは完全に論理的です。さらに、ループの各反復で変数の自然な「初期」値はありません-forループhasが変数を宣言する形式であることは言うまでもありませんイニシャライザー、条件でテストし、イテレーターで変更します。このようなループで何ができると思いますか?

for (int i = 0, j = 10; i < j; i++)
{
    if (someCondition)
    {
        j++;
    }
    actions.Add(() => Console.WriteLine(i, j));
}

これを、反復ごとに個別の変数を宣言しているようにlooksであるforeachループと比較してください。ちなみに、変数は読み取り専用であり、偶数になりますmore反復ごとに変化する1つの変数であると考えると奇妙です。 foreachループを、イテレーターから取得した値を使用して、各反復で新しい読み取り専用変数を宣言するものと考えるのは完全に理にかなっています。

44
Jon Skeet