86

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

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

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

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

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

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

4

3 に答える 3

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

于 2013-02-17T02:18:55.627 に答える
12

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

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

変更した場合

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

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

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

于 2015-06-03T01:07:23.427 に答える
8

ブロックスコープの変数は警告を解決する必要があります。

foreach (var entry in entries)
{
   var en = entry; 
   var result = DoSomeAction(o => o.Action(en));
}
于 2016-05-26T12:34:06.350 に答える