질문자 :StriplingWarrior
C#에서 람다 식이나 익명 메서드를 사용할 때 수정된 클로저 함정에 접근하는 것을 조심해야 합니다. 예를 들어:
foreach (var s in strings) { query = query.Where(i => i.Prop == s); // access to modified closure ... }
수정된 클로저로 인해 위의 코드는 Where
s
의 최종 값을 기반으로 하도록 합니다.
여기 에 설명된 대로 foreach
루프에서 선언된 s
변수가 컴파일러에서 다음과 같이 번역되기 때문에 발생합니다.
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#에서 최악의 "문제점" 중 하나이며 우리는 이를 수정하기 위해 주요 변경 사항을 사용할 것입니다. C# 5에서 foreach 루프 변수는 논리적으로 루프 본문 내부 에 있으므로 클로저는 매번 새로운 복사본을 얻습니다.
for
루프는 변경되지 않으며 변경 사항은 이전 버전의 C#으로 "백포팅"되지 않습니다. 따라서 이 관용구를 사용할 때 계속 주의해야 합니다.
Eric Lippert문의하신 내용은 Eric Lippert가 자신의 블로그 게시물인 Closing over the loop variable(유해한 것으로 간주되는 루프 변수 및 그 속편)에서 철저하게 다룹니다.
나에게 가장 설득력 있는 주장은 각 반복에서 새로운 변수를 갖는 것이 for(;;)
스타일 루프와 일치하지 않는다는 것입니다. 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 변수에 대한 폐쇄 .
Krizz이것에 물린 후, 나는 어떤 클로저로 전송하는 데 사용하는 가장 안쪽 범위에 로컬로 정의된 변수를 포함하는 습관이 있습니다. 귀하의 예에서 :
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. }
그런 습관이 있으면 실제로 외부 범위에 바인딩하려는 매우 드문 경우에 이를 피할 수 있습니다. 솔직히 말해서 한 번도 해본 적이 없는 것 같아요.
GodekeC# 5.0에서는 이 문제가 수정되었으며 루프 변수를 닫고 예상한 결과를 얻을 수 있습니다.
언어 사양은 다음과 같이 말합니다.
8.8.4 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 } }
(...)
while 루프 내부에 v
배치하는 것은 임베디드 문에서 발생하는 익명 함수에 의해 캡처되는 방식에 중요합니다. 예를 들어:
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 루프 이후의 값은 f
13
이 됩니다. 대신, 각 반복에는 고유한 변수 v
f
에 의해 캡처된 7
을 유지하며, 이 값이 인쇄됩니다. ( 참고: C#의 이전 버전은 while 루프 외부에서 v
선언했습니다. )
Paolo Moretti출처 : http:www.stackoverflow.com/questions/8898925/is-there-a-reason-for-cs-reuse-of-the-variable-in-a-foreach