JetBrains Rider 2024.1 ヘルプ

コード検査: 変更されたキャプチャー変数へのアクセス

まず、クロージャ(英語)とは何かを理解していることを確認しましょう。簡単に言うと、C# のクロージャは、ラムダ式または外部スコープからいくつかの変数をキャプチャーする匿名メソッドです。最も簡単な例を次に示します。

// A self-contained lambda. Not a closure. Action printOne = () => { Console.WriteLine("one"); }; // A closure – a lambda that captures a variable from an outer scope. string myStr = "one"; Action print = () => { Console.WriteLine(myStr); };

上記の例では、print は変数 myStr (そのではなく)をキャプチャーし、print() を呼び出したときにのみ myStrを取得します。

より複雑なシナリオでは、クロージャが変化するコンテキストで定義されると、期待通りに動作しないことがあります。

ループ内で上記のクロージャを定義する例を次に示します。

var myActions = new List<Action>(); var myStrings = new List<string>() { "one", "two", "three" }; for (var i = 0; i < myStrings.Count; i++) { Action print = () => { Console.WriteLine(myStrings[i]); }; myActions.Add(print); } myActions[0]();

意外にも、このコードは、myActions[0](); を呼び出すときに ArgumentOutOfRangeException を生成します。直感的に見えるかもしれない Console.WriteLine(myStrings[0]); を実行する代わりに、この呼び出しは Console.WriteLine(myStrings[i]); を実行しようとします。ifor サイクル全体にスコープされ、その値は 0 に等しくなく、2 にも等しくない(これは条件本当だった)。最後の ++ 演算の結果、条件が偽になる直前の値が 3 になり、ループを終了しました。myStrings は 3 つの要素しか持たないため、myStrings[3]ArgumentOutOfRangeException につながります。

JetBrains Rider は、ここでは ArgumentOutOfRangeException の形をとる結果を推論しませんが、問題の原因 (クロージャ内の反復変数) を正しく指摘し、変化する変数の値をスコープにコピーすることで問題を修正することを提案します。クロージャが定義されている場所:

for (var i = 0; i < myStrings.Count; i++) { var i1 = i; Action print = () => { Console.WriteLine(myStrings[i1]); }; myActions.Add(print); }

この修正により、myActions からアクションを選択し、このアクションが作成されたコンテキストを取得すると、i1 はリスト内のアクションのインデックスに対応する値を保持します。