C#がforeachで変数を再利用する理由はありますか?



Is There Reason C Reuse Variables Foreach



この記事の翻訳元: C#がforeachで変数を再利用する理由はありますか?

C#でラムダ式または匿名メソッドを使用する場合は、 変更されたクロージャへのアクセス 落とし穴。 C#でラムダ式または匿名メソッドを使用する場合は、注意が必要です。 変更されたクロージャ 閉じ込められた アクセス 例えば: 例えば:



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

クロージャが変更されたため、上記のコードによりすべてのWhereが発生します。 sの最終値に基づくクエリの句。 クロージャーの変更により、上記のコードは、Where句がs最終値に基づいてすべてのクエリを実行します。

説明したように ここに 、これはsが原因で発生しますforeachで宣言された変数上記のループは、コンパイラで次のように変換されます。 説明したように ここにあります 、これは、in s変数がin 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ループで定義された変数をループ外で使用できない場合:



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

そのため、コンパイラーは、認識できる利点を生み出さずに、見つけてデバッグするのが難しいことが多いエラーが発生しやすい方法で変数を宣言します。 したがって、コンパイラーは特定の方法で変数を宣言し、明らかな利点を生み出すことなく、通常は見つけてデバッグするのが難しいエラーが非常に発生しやすくなります。

foreachでできることはありますか内部スコープの変数を使用してコンパイルした場合は不可能だったこの方法でループしますか、それとも匿名メソッドとラムダ式が使用可能または共通になる前に行われた任意の選択であり、それ以降は改訂されていませんか? 使用できますかforeachループはこのように処理されます。内部スコープ変数を使用してコンパイルされている場合は実行できません。または、匿名メソッドとラムダ式が使用可能または一般化される前に任意の選択が行われ、それ以来変更されていませんか?


#1階

参照: https://stackoom.com/question/bL13/C-foreachで変数を再利用する理由はありますか


#2階

C#5.0では、この問題は修正されており、ループ変数を閉じて、期待する結果を得ることができます。 C#5.0では、この問題が修正されました。ループ変数を閉じて、目的の結果を得ることができます。

言語仕様には次のように書かれています。 言語仕様には次のように書かれています。

8.8.4foreachステートメント 8.8.4foreachステートメント

(...) (......)

フォームのforeachステートメント Foreachステートメント

foreach (V v in x) embedded-statement

次に、次のように展開されます。 次に、次のように展開します。

{ E e = ((C)(x)).GetEnumerator() try { while (e.MoveNext()) { V v = (V)(T)e.Current embedded-statement } } finally { … // Dispose e } }

(...) (......)

vの配置whileループ内は、埋め込みステートメントで発生する無名関数によってどのようにキャプチャされるかにとって重要です。 v whileループ内の位置は、埋め込みステートメントに表示される無名関数がそれをキャプチャする方法にとって重要です。 例えば: 例えば:

int[] values = { 7, 9, 13 } Action f = null foreach (var value in values) { if (f == null) f = () => Console.WriteLine('First value: ' + value) } f()

vの場合whileループの外側で宣言された場合、すべての反復で共有され、forループの後の値が最終値13になります。 、これがfの呼び出しです印刷します。 case v whileループの外側で宣言された場合、すべての反復で共有され、forループの後の値が最終値になります13 、これは正しいですf呼び出しの出力。 代わりに、各反復には独自の変数がありますvfによってキャプチャされたもの最初の反復では、値7を保持し続けます、これが印刷されます。 代わりに、各反復には独自の変数があるためv 、したがって、最初の反復でfキャプチャされた変数は値を保持し続けます7 、値が出力されます。 (( 注:以前のバージョンのC#はvを宣言しましたwhileループの外側。 )。 (( 注:以前のバージョンのC#は、whileループの外側で宣言されていますv )。


#3階

私の意見では、それは奇妙な質問です。 これは奇妙な問題だと思います。 コンパイラがどのように機能するかを知ることは良いことですが、それは「知っておくべきこと」だけです。 コンパイラがどのように機能するかを知ることは良いことですが、「知ること」だけです。

コンパイラのアルゴリズムに依存するコードを書く場合、それは悪い習慣です。そして、この依存関係を除外するようにコードを書き直すことをお勧めします。 悪い習慣であるコンパイラアルゴリズムに依存するコードを書く場合は、この依存関係を排除するためにコードを書き直すのが最善です。

これは就職の面接に良い質問です。 これは就職の面接にとって良い質問です。 しかし、実生活では、就職の面接で解決した問題に直面したことはありません。 しかし、実際には、インタビューで解決された問題は発生していません。

foreachの90%は、コレクションの各要素の処理に使用されます(一部の値の選択や計算には使用されません)。 foreachの90%は、コレクションの各要素の処理に使用されます(特定の値の選択または計算には適していません)。 ループ内でいくつかの値を計算する必要がある場合もありますが、BIGループを作成することはお勧めできません。 ループ内でいくつかの値を計算する必要がある場合もありますが、BIGループを作成することは良い習慣ではありません。

値の計算にはLINQ式を使用することをお勧めします。 LINQ式を使用して値を計算することをお勧めします。 ループ内で多くのことを計算するとき、あなた(または他の誰か)がこのコードを読む2〜3か月後、人はこれが何であり、どのように機能するかを理解できません。 ループ内で多くのことを計算するとき、あなた(または他の誰か)が2〜3か月後にこのコードを読むと、人々はこれが何であるか、そしてそれがどのように機能するかを理解できなくなります。


#4階

あなたが求めていることは、EricLippertのブログ投稿で完全にカバーされています 有害と見なされるループ変数を閉じる とその続編。 Eric Lippertは、彼のブログ投稿であなたが尋ねている質問を完全にカバーしています。 有害と見なされる閉ループ変数 そしてその後継者。

私にとって最も説得力のある議論は、各反復で新しい変数を持つことはfor()と矛盾するということです。スタイルループ。 私にとって最も説得力のある議論は、各反復で新しい変数があり、for()パターンサイクルに一貫性がないということです。 新しいint iがあると思いますかfor (int i = 0 i <10 i++)の各反復で? 必要ですかfor (int i = 0 i <10 i++)各反復には新しいint iがあります?

この動作の最も一般的な問題は、反復変数を閉じることであり、簡単な回避策があります。 この動作の最も一般的な問題は反復変数のクロージャであり、簡単な解決策があります。

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

この問題に関する私のブログ投稿: C#のforeach変数のクロージャ この問題に関する私のブログ投稿: C#のforeach変数は閉じられています


#5階

これに噛まれたので、クロージャーに転送するために使用する最も内側のスコープにローカルで定義された変数を含める習慣があります。 この問題に悩まされた後、クロージャーに渡すために使用する最も内側のスコープにローカルで定義された変数を含める習慣があります。 あなたの例では: あなたの例では:

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

私がやります: 私がやります:

foreach (var s in strings) { string search = s query = query.Where(i => i.Prop == search) // New definition ensures unique per iteration.

あなたがその習慣を持ったら、あなたはそれを避けることができます 非常に 実際に外部スコープにバインドすることを意図したまれなケース。 この習慣を身に付けたら、実際の計画では外部スコープにバインドします ポール いくつかのケースでは、この習慣を避けることができます。 正直なところ、今までそうしたことはないと思います。 正直なところ、私はこれをやったことがないと思います。


#6階

コンパイラーは、認識できる利点を生み出さずに、見つけてデバッグするのが難しいことが多いエラーが発生しやすい方法で変数を宣言します。 コンパイラーは、明らかな利点を生み出すことなく、通常は見つけてデバッグするのが難しいエラーが非常に発生しやすい方法で変数を宣言します。

あなたの批判は完全に正当化されます。 あなたの批判は完全に正当化されます。

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

有害と見なされるループ変数を閉じる ループ変数を閉じることは有害であると見なされます

この方法でforeachループを使用して、内部スコープの変数を使用してコンパイルした場合には実行できなかったことがありますか? foreachループを使用して、内部スコープ変数を介してコンパイルすることは可能ですが、それはできませんか? それとも、これは匿名メソッドとラムダ式が利用可能または一般的になる前に行われた任意の選択であり、それ以降は改訂されていませんか? それとも、これは匿名メソッドとラムダ式が利用可能またはユニバーサルになる前に行われた任意の選択であり、それ以降変更されていませんか?

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

その決断を後悔していると言っても過言ではないでしょう。 誰もが決断を後悔していると言えます。 これはC#で最悪の「落とし穴」の1つであり、 重大な変更を加えて修正します。 これは、C#で最悪の「落とし穴」の1つです。 それを修正するために大きな変更を加えます。 C#5では、foreachループ変数は論理的になります 内部 ループの本体であるため、クロージャは毎回新しいコピーを取得します。 C#5では、foreachループ変数が論理的に変更されます 寝転ぶ ループ内では、クロージャーは毎回新しいコピーを取得します。

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