다음과 같은 경고가 표시됩니다.
클로저에서 foreach 변수에 액세스합니다. 다른 버전의 컴파일러로 컴파일하면 다른 동작이 발생할 수 있습니다.
내 편집기에서 다음과 같이 보입니다.
이 경고를 수정하는 방법을 알고 있지만이 경고가 표시되는 이유를 알고 싶습니다.
“CLR”버전에 관한 것입니까? “IL”과 관련이 있습니까?
답변
이 경고에는 두 부분이 있습니다. 첫 번째는 …
클로저에서 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
(우리가 닫은) 값을 인쇄하십시오 . 실제로는 동일한 기능 ( “인쇄 s
“, “인쇄 s
“, “인쇄 s
“…)입니다.
우리가 그것들을 사용하기 위해가는 시점에서 그들은 우리가 요구하는 것을 정확하게 수행합니다 : s
. 당신의 마지막 알려진 값을 보면 s
, 당신은 그것의 것을 볼 수 있습니다 5
. 그래서 우리 s == 5
는 콘솔에 5 번 인쇄됩니다.
정확히 우리가 요청한 것이지만 우리가 원하는 것은 아닐 것입니다.
경고의 두 번째 부분은 …
다른 버전의 컴파일러로 컴파일하면 다른 동작이 발생할 수 있습니다.
… 그것입니다. C # 5부터 컴파일러는 .NET을 통해 이러한 일이 발생하지 않도록 “방지”하는 다른 코드를 생성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
C # 5 의 변경 사항 이이 문제를 방지하는 데 도움이되지만 완전히는 아닙니다.
답변
첫 번째 대답은 훌륭해서 한 가지만 추가하겠다고 생각했습니다.
예제 코드에서 reflectModel에 IEnumerable이 할당 되었기 때문에 경고가 발생합니다. IEnumerable은 열거 할 때만 평가되며, reflectModel을 더 넓은 범위의 항목에 할당하면 열거 자체가 루프 외부에서 발생할 수 있습니다. .
당신이 변했다면
...Where(x => x.Name == property.Value)
…에
...Where(x => x.Name == property.Value).ToList()
그런 다음 reflectModel에 foreach 루프 내에서 명확한 목록이 할당되므로 열거가 루프 외부가 아닌 루프 내에서 확실히 발생하므로 경고를받지 못할 것입니다.
답변
블록 범위 변수는 경고를 해결해야합니다.
foreach (var entry in entries)
{
var en = entry;
var result = DoSomeAction(o => o.Action(en));
}