1. ホーム
  2. c#

[解決済み] クロージャでのforeach変数へのアクセスに関する警告

2023-05-11 20:34:29

質問

以下のような警告が表示されます。

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

エディタで見るとこんな感じです。

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

CLRのバージョンに関するものですか?ILと関係があるのでしょうか?

どのように解決するのですか?

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

クロージャ内の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 (の値を表示するという、まったく同じことをする5つの関数を作成したからです。) 実際には、これらは同じ関数です("Print s "、"プリント s "、"プリント s "・・・)。

それらを使おうとする時点で、それらはまさに私たちが要求することを行います。 s . の最後の既知の値を見てみましょう。 s を見ると、それが 5 . というわけで、次のようになります。 s == 5 はコンソールに5回出力されます。

これはまさに私たちが求めたものですが、おそらく私たちが望むものではありません。

警告の第二弾は...

異なるバージョンのコンパイラでコンパイルした場合、異なる挙動を示すことがあります。

...は、その通りです。 C# 5 からは、コンパイラが別のコードを生成して、"prevent" を経由してこれが発生しないようになりました。 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"で起こるそうです。そのため おそらく、.NET 4.5 以降をターゲットにする必要があります。 をターゲットとし、C# 5 以降のコンパイラーを使用しなければ新しい動作は得られず、それ以前のものはすべて古い動作になります。

しかし、明確には、これはコンパイラーの機能であり、.NET Framework のバージョンではありません。

ILとの関連性はあるのでしょうか?

コードが違えば生成されるILも違うので、その意味では生成されるILに結果があります。

1 foreach は、あなたがコメントで投稿したコードよりもはるかに一般的な構成です。この問題は、通常 foreach を使用することで発生するもので、手動で列挙することで発生するものではありません。そのため foreach の変更はこの問題を防ぐのに役立ちますが、完全ではありません。