コードインスペクション: 変更されたキャプチャー変数へのアクセス
まず、 クロージャ(英語)とは何かを理解していることを確認しましょう。 簡単に言うと、C# のクロージャは、ラムダ式または外部スコープからいくつかの変数をキャプチャーする匿名メソッドです。 最も簡単な例を次に示します。
上記の例では、 print は変数 myStr (その 値ではなく)をキャプチャーし、 print() を呼び出したときにのみ myStr の 値を取得します。
より複雑なシナリオでは、クロージャが変化するコンテキストで定義されると、期待通りに動作しないことがあります。
ループ内で上記のクロージャを定義する例を次に示します。
驚いたことに、このコードは ArgumentOutOfRangeException を myActions[0](); を呼び出すと生成します。 ここで起こるのは次のとおりです。直感的に感じられる Console.WriteLine(myStrings[0]); の実行ではなく、この呼び出しは Console.WriteLine(myStrings[i]); を実行しようとします。そして、 i が for 全体のサイクルにスコープされているため、その値は 0 と等しくなく、さらに 2 とも異なります(直前に条件が真だったときの値)。 最後の ++ 操作の結果、条件が偽になりループを抜ける直前に値は 3 になりました。 myStrings が 3 つの要素しか持たないため、 myStrings[3] は ArgumentOutOfRangeException につながります。
JetBrains Rider は結果を推測しません(ここでは ArgumentOutOfRangeException の形になります)が、問題の原因であるクロージャ内のイテレーション変数を正しく指摘し、クロージャが定義されているスコープに変更される変数の値をコピーすることで修正することを提案しています:
この修正により、 myActions からアクションを選択し、このアクションが作成されたコンテキストを取得すると、 i1 はリスト内のアクションのインデックスに対応する値を保持します。