1. ホーム
  2. c#

[解決済み] C#がforeachで変数を再利用するのは理由があるのか?

2022-03-19 20:06:04

質問内容

C#でラムダ式や匿名メソッドを使用する際、注意しなければならないのが 変更されたクロージャへのアクセス の落とし穴です。例えば

foreach (var s in strings)
{
   query = query.Where(i => i.Prop == s); // access to modified closure
   ...
}

クロージャが変更されたため、上記のコードでは、すべての Where の最終的な値に基づいています。 s .

説明したように これ があるため、このようなことが起こります。 s で宣言された変数は foreach のループは、コンパイラで次のように翻訳されます。

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}

ではなく、このように

while (enumerator.MoveNext())
{
   string s;
   s = enumerator.Current;
   ...
}

ご指摘の通り これ ループの外側で変数を宣言することにパフォーマンス上の利点はありませんし、通常であれば、ループの範囲外で変数を使用する予定がある場合のみ、宣言する理由が考えられます。

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}
var finalString = s;

ただし foreach ループの外側で使用することはできません。

foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.

そのため、コンパイラはこの変数を、発見とデバッグが困難なエラーが発生しやすい方法で宣言し、その一方で、目に見える利点は何も生み出さないのです。

でできることはないのでしょうか? foreach それとも、匿名メソッドやラムダ式が一般的になる前に作られた恣意的な選択で、それ以降も変更されていないのでしょうか?

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

<ブロッククオート

コンパイラは、発見とデバッグが困難なエラーが発生しやすい方法で変数を宣言していますが、目に見える利点はありません。

あなたの批判はまったく正当なものです。

この問題については、こちらで詳しく説明しています。

有害と思われるループ変数上のクローズ

foreachループをインナースコープ変数でコンパイルした場合にはできないことが、この方法ではできるのでしょうか?それとも、匿名メソッドやラムダ式が一般的になる前に行われ、その後も改訂されていない、単なる恣意的な選択なのでしょうか?

後者です。C# 1.0仕様では、ループ変数がループ本体の内側にあるか外側にあるかは、観察可能な違いがないため、実は明言されていませんでした。C# 2.0でクロージャーのセマンティクスが導入されたとき、ループ変数をループの外側に置くという選択がなされ、"for"ループと一致するようになったのです。

その判断は、誰もが後悔していると言っていいと思います。これは、C#における最悪のゴッチャの1つであり、また 私たちは、それを修正するために、ブレークチェンジを行うつもりです。 C# 5では、foreachループの変数は、論理的に 内側 そのため、クロージャは毎回新しいコピーを取得します。

for ループは変更されず、その変更は以前のバージョンの C# に "バックポート" されません。したがって、このイディオムを使用する場合は、引き続き注意が必要です。