[C#] foreach에서 C #이 변수를 재사용하는 이유가 있습니까?

C #에서 람다 식 또는 익명 메서드를 사용하는 경우 수정 된 클로저 함정 에 대한 액세스에 주의해야합니다 . 예를 들면 다음과 같습니다.

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

수정 된 클로저로 인해 위의 코드는 Where쿼리 의 모든 절이의 최종 값을 기반으로하도록합니다 s.

here 설명 된 것처럼 위의 루프 s에서 선언 된 변수 foreach가 컴파일러에서 다음과 같이 변환 되기 때문에 발생 합니다.

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

이 대신에 :

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

here 에서 지적했듯이 루프 외부에서 변수를 선언하면 성능 이점이 없으며 일반적인 상황에서 루프 범위 밖에서 변수를 사용하려는 경우이를 생각할 수있는 유일한 이유는 다음과 같습니다.

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 #에서 최악의 “gotchas”중 하나이며 이를 수정하기 위해 주요 변경 사항을 적용 할 것입니다. C # 5에서 foreach 루프 변수는 논리적으로 루프 본문 안에 있으므로 클로저는 매번 새로운 사본을 얻습니다.

for루프가 변경되지 않으며, 변경이되지 않습니다 C #을 이전 버전으로 “다시는 포팅”. 따라서이 관용구를 사용할 때는 계속주의해야합니다.


답변

에릭 리퍼 (Eric Lippert)는 자신의 블로그 포스트에서 유해한 것으로 간주되는 루프 변수에 대한 Closing 과 그 속편을 철저히 다루었습니다 .

나에게 가장 설득력있는 주장은 각 반복에서 새로운 변수를 갖는 것이 for(;;)스타일 루프 와 일치하지 않는다는 것 입니다. int i반복 할 때마다 새로운 것을 기대하겠습니까 for (int i = 0; i < 10; 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 변수에 대한 폐쇄 .


답변

이것에 물린 후, 나는 가장 가까운 범위에 로컬로 정의 된 변수를 포함시키는 습관을 가지고 있습니다. 귀하의 예에서 :

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.
}        

일단 그 습관을 가지면, 실제로는 외부 스코프에 바인딩하려는 아주 드문 경우에 이를 피할 수 있습니다 . 솔직히 말해서, 나는 그렇게 한 적이 없다고 생각합니다.


답변

C # 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
  }
}

(…)

v내부 루프 의 배치 는 임베디드 명령문에서 발생하는 익명 함수에 의해 캡처되는 방법에 중요합니다. 예를 들면 다음과 같습니다.

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

vwhile 루프 외부에서 선언 된 경우 모든 반복간에 공유되며 for 루프 이후의 값은 최종 값 이됩니다. 13이 값 은 호출 f이 인쇄 하는 것입니다. 대신, 각 반복마다 고유 한 변수 가 있으므로 첫 번째 반복에서 v캡처 한 변수 f는 계속 값을 유지하여
7인쇄됩니다. ( 참고 : 이전 버전의 C # v은 while 루프 외부에서 선언되었습니다 . )


답변