クロージャ警告のforeach変数へのアクセス


85

次の警告が表示されます。

クロージャ内のforeach変数へのアクセス。異なるバージョンのコンパイラでコンパイルすると、動作が異なる場合があります。

これは私のエディターでどのように見えるかです:

ホバーポップアップの上記のエラーメッセージ

この警告を修正する方法は知っていますが、なぜこの警告が表示されるのか知りたいのですが。

これは「CLR」バージョンについてですか?「IL」と関係がありますか?



1
TL; DR回答:クエリ式の最後に.ToList()または.ToArray()を追加すると、警告が
削除され

回答:


136

この警告には2つの部分があります。最初は...

クロージャ内のforeach変数へのアクセス

...それ自体は無効ではありませんが、一見すると直感に反します。また、正しく行うことは非常に困難です。(以下にリンクする記事では、これを「有害」と説明しているほどです。)

クエリを実行します。抜粋したコードは、基本的に、C#コンパイラ(C#5より前)がforeach1に対して生成するものの拡張形式であることに注意してください。

なぜ[以下が]無効なのか[わかりません]:

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(閉じた)の値を出力します。実際には、これらは同じ機能です(「印刷s」、「印刷s」、「印刷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での変更はこの問題の防止に役立ちますが、完全ではありません。


7
私は実際に異なるコンパイラでforeachループを試し、同じターゲット(.Net 3.5)を使用して異なる結果を得ました。私はVS2010(私が信じる.net 4.0に関連付けられたコンパイラーを使用する)とVS2012(私が信じる.net 4.5コンパイラー)を使用しました。原則として、これは、VS2013を使用して.Net 3.5を対象とするプロジェクトを編集し、少し古いフレームワークがインストールされているビルドサーバーでビルドした場合、マシンとデプロイされたビルドでプログラムの結果が異なる可能性があることを意味します。
ykok 2014年

良い答えですが、「foreach」がどのように関連しているかはわかりません。これは、手動の列挙、または単純なfor(int i = 0; i <collection.Size; i ++)ループでも発生しませんか?クロージャがスコープ外になることの問題、より正確には、クロージャが内部で定義されたスコープ外になるときのクロージャの動作を理解することの問題のようです。
ブラッド

ここにあるforeachものは質問の内容から来ています。あなたはそれがさまざまな、より一般的な方法で起こり得るということは正しいです。
ta.speot.is 2014

1
R#がまだ警告を表示するのはなぜですか、4.5に設定したターゲットフレームワークを読み取れません。
Johnny_D 2014年

1
「したがって、おそらく.NET4.5以降をターゲットにする必要があります」このステートメントは正しくありません。ターゲットとする.NETのバージョンはこれに影響しません。コンパイルにC#5(VS 2012以降)を使用している場合、.NET 2.0、3.5、および4でも動作が変更されます。そのため、この警告は.NET 4.0以前でのみ表示されます。4.5をターゲットにしている場合、C#4以前のコンパイラでは4.5をコンパイルできないため、警告は表示されません。
スコットチェンバレン

12

最初の答えは素晴らしいので、1つだけ追加したいと思いました。

サンプルコードでは、reflectedModelにIEnumerableが割り当てられているため、警告が表示されます。これは、列挙時にのみ評価されます。より広いスコープを持つものにreflectedModelを割り当てた場合、列挙自体がループの外側で発生する可能性があります。 。

変更した場合

...Where(x => x.Name == property.Value)

...Where(x => x.Name == property.Value).ToList()

次に、reflectedModelにはforeachループ内で明確なリストが割り当てられるため、列挙はループの外側ではなくループ内で確実に行われるため、警告は表示されません。


この問題を解決できなかった非常に長い説明をたくさん読んだ後、解決した短い説明を1つ読みました。ありがとう!
チャールズクレイトン

受け入れられた答えを読んで、「変数をバインドしていない場合、どのようにクロージャになるのか」と考えました。でも今は評価がいつ起こるかわかりました、ありがとう!
ジェローム

はい、これは明らかな普遍的な解決策です。低速でメモリを大量に消費しますが、すべてのケースで実際に100%機能していると思います。
アルケップ2016年

8

ブロックスコープの変数は警告を解決するはずです。

foreach (var entry in entries)
{
   var en = entry; 
   var result = DoSomeAction(o => o.Action(en));
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.