次の警告が表示されます。
クロージャ内のforeach変数へのアクセス。異なるバージョンのコンパイラでコンパイルすると、動作が異なる場合があります。
これは私のエディタでどのように見えるかです:
この警告を修正する方法は知っていますが、なぜこの警告が表示されるのか知りたいのですが。
これは「CLR」バージョンについてですか?「IL」と関係がありますか?
この警告には2つの部分があります。最初は...
クロージャ内のforeach変数へのアクセス
...それ自体は無効ではありませんが、一見すると直感に反します。また、正しく行うのは非常に困難です。(以下にリンクする記事では、これを「有害」と説明しているほどです。)
クエリを実行します。抜粋したコードは、基本的に、C#コンパイラ(C#5より前)がforeach
1に対して生成するものの拡張形式であることに注意してください。
私は[以下が]無効である理由を[理解していません]:
string s; while (enumerator.MoveNext()) { s = enumerator.Current; ...
まあ、それは構文的に有効です。そして、ループで行っているのがの値を使用しているだけであればs
、すべてが良好です。しかし、閉鎖するs
と、直感に反する行動につながります。次のコードを見てください。
var countingActions = new List<Action>();
var numbers = from n in Enumerable.Range(1, 5)
select n.ToString(CultureInfo.InvariantCulture);
using (var enumerator = numbers.GetEnumerator())
{
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
Console.WriteLine("Creating an action where s == {0}", s);
Action action = () => Console.WriteLine("s == {0}", s);
countingActions.Add(action);
}
}
このコードを実行すると、次のコンソール出力が得られます。
Creating an action where s == 1
Creating an action where s == 2
Creating an action where s == 3
Creating an action where s == 4
Creating an action where s == 5
これはあなたが期待することです。
おそらく予期しないものを表示するには、上記のコードの直後に次のコードを実行します。
foreach (var action in countingActions)
action();
次のコンソール出力が表示されます。
s == 5
s == 5
s == 5
s == 5
s == 5
なんで?すべてがまったく同じことを行う5つの関数を作成したため、s
(閉じた)の値を出力します。実際には、これらは同じ機能です( "Print s
"、 "Print s
"、 "Print s
" ...)。
それらを使用する時点で、それらは私たちが求めることを正確に実行します。の値を出力しs
ます。の最後の既知の値をs
見ると、それがであることがわかります5
。したがってs == 5
、コンソールに5回印刷されます。
これはまさに私たちが求めていたものですが、おそらく私たちが望んでいるものではありません。
警告の2番目の部分...
異なるバージョンのコンパイラでコンパイルすると、動作が異なる場合があります。
...それが何であるかです。C#5以降、コンパイラは、を介してこれが発生するのを「防ぐ」さまざまなコードを生成しforeach
ます。
したがって、次のコードは、コンパイラのバージョンが異なると、異なる結果を生成します。
foreach (var n in numbers)
{
Action action = () => Console.WriteLine("n == {0}", n);
countingActions.Add(action);
}
その結果、R#警告も生成されます:)
上記の最初のコードスニペットは、使用していないため、コンパイラのすべてのバージョンで同じ動作を示しますforeach
(むしろ、C#5以前のコンパイラと同じように拡張しました)。
これはCLRバージョン用ですか?
ここで何を求めているのかよくわかりません。
Eric Lippertの投稿によると、変更は「C#5で」行われます。それでおそらく、.NET4.5以降をターゲットにする必要がありますC#5以降のコンパイラを使用して新しい動作を取得し、それ以前のすべてが古い動作を取得します。
ただし、明確にするために、これはコンパイラの機能であり、.NETFrameworkのバージョンではありません。
ILとの関連性はありますか?
異なるコードは異なるILを生成するため、その意味で、生成されたILに影響があります。
1 foreach
は、コメントに投稿したコードよりもはるかに一般的な構成です。この問題は通常foreach
、手動の列挙ではなく、の使用によって発生します。foreach
そのため、C#5での変更はこの問題の防止に役立ちますが、完全ではありません。
最初の答えは素晴らしいので、1つだけ追加したいと思いました。
サンプルコードでは、reflectedModelにIEnumerableが割り当てられているため、警告が表示されます。これは、列挙時にのみ評価されます。より広いスコープを持つものにreflectedModelを割り当てた場合、列挙自体がループの外側で発生する可能性があります。 。
変更した場合
...Where(x => x.Name == property.Value)
に
...Where(x => x.Name == property.Value).ToList()
次に、reflectedModelにはforeachループ内で明確なリストが割り当てられるため、列挙はループの外側ではなくループ内で確実に行われるため、警告は表示されません。
ブロックスコープの変数は警告を解決する必要があります。
foreach (var entry in entries)
{
var en = entry;
var result = DoSomeAction(o => o.Action(en));
}