종료 경고에서 foreach 변수에 액세스


86

다음과 같은 경고가 표시됩니다.

클로저에서 foreach 변수에 액세스합니다. 다른 버전의 컴파일러로 컴파일하면 다른 동작이 발생할 수 있습니다.

내 편집기에서 다음과 같이 보입니다.

호버 팝업에서 위에서 언급 한 오류 메시지

이 경고를 수정하는 방법을 알고 있지만이 경고가 표시되는 이유를 알고 싶습니다.

"CLR"버전에 관한 것입니까? "IL"과 관련이 있습니까?



1
TL; DR 답변 : 쿼리 표현식 끝에 .ToList () 또는 .ToArray ()를 추가하면 경고가 제거됩니다
JoelFan

답변:


136

이 경고에는 두 부분이 있습니다. 첫 번째는 ...

클로저에서 foreach 변수에 대한 액세스

... 그것은 그 자체로 유효하지 않지만 언뜻보기에는 반 직관적입니다. 올바른 일을하는 것도 매우 어렵습니다. (아래에 링크 한 기사에서 "유해한"것으로 설명합니다.)

발췌 한 코드는 기본적으로 C # 컴파일러 (C # 5 이전)가 foreach1에 대해 생성하는 것의 확장 된 형식이라는 점에 유의하십시오 .

나는 [다음이] 유효하지 않은 이유를 이해하지 못합니다.

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수동 열거가 아닌를 사용하여 발생합니다 . 그렇기 때문에 foreachC # 5 의 변경 사항 이이 문제를 방지하는 데 도움이되지만 완전히는 아닙니다.


7
실제로 동일한 대상 (.Net 3.5)을 사용하여 다른 결과를 얻는 다른 컴파일러에서 foreach 루프를 시도했습니다. 나는 VS2010 (내가 믿는 .net 4.0과 관련된 컴파일러를 사용함)과 VS2012 (내가 믿는 .net 4.5 컴파일러)를 사용했습니다. 원칙적으로 이것은 VS2013을 사용하고 .Net 3.5를 대상으로하는 프로젝트를 편집하고 약간 오래된 프레임 워크가 설치된 빌드 서버에서 빌드하는 경우 배포 된 빌드와 컴퓨터에서 프로그램의 다른 결과를 볼 수 있음을 의미합니다.
Ykok

좋은 대답이지만 "foreach"가 얼마나 관련이 있는지 확실하지 않습니다. 수동 열거 나 단순한 for (int i = 0; i <collection.Size; i ++) 루프에서도 이런 일이 발생하지 않습니까? 클로저가 범위를 벗어나는 문제, 더 정확하게는 클로저가 내부에 정의 된 범위를 벗어날 때 클로저가 어떻게 작동하는지 이해하는 문제인 것 같습니다.
Brad

foreach물건은 여기에 질문의 내용에서 비롯됩니다. 다양하고 일반적인 방법으로 일어날 수 있다는 것이 맞습니다.
ta.speot.is jul.

1
R #이 여전히 경고를 표시하는 이유는 4.5로 설정 한 대상 프레임 워크를 읽지 않습니다.
Johnny_D 2014

1
"따라서 .NET 4.5 이상을 대상으로해야 합니다. " 이 진술은 사실이 아닙니다. 대상 .NET 버전은 이에 영향을 미치지 않으며 컴파일에 C # 5 (VS 2012 이상)를 사용하는 경우 .NET 2.0, 3.5 및 4에서도 동작이 변경됩니다. 그렇기 때문에 .NET 4.0 또는 이전 버전에서만이 경고가 표시되고 4.5를 대상으로하는 경우 C # 4 또는 이전 컴파일러에서 4.5를 컴파일 할 수 없기 때문에 경고가 표시되지 않습니다.
Scott Chamberlain

12

첫 번째 대답은 훌륭해서 한 가지만 추가하겠다고 생각했습니다.

예제 코드에서 reflectModel에 IEnumerable이 할당 되었기 때문에 경고가 발생합니다. IEnumerable은 열거 할 때만 평가되며, reflectModel을 더 넓은 범위의 항목에 할당하면 열거 자체가 루프 외부에서 발생할 수 있습니다. .

당신이 변했다면

...Where(x => x.Name == property.Value)

...에

...Where(x => x.Name == property.Value).ToList()

그런 다음 reflectModel에 foreach 루프 내에서 명확한 목록이 할당되므로 열거가 루프 외부가 아닌 루프 내에서 확실히 발생하므로 경고를받지 못할 것입니다.


저는이 문제를 해결하지 못한 정말 긴 설명을 많이 읽었고, 그 다음에는 해결 된 짧은 설명을 읽었습니다. 감사!
Charles Clayton

나는 받아 들여진 대답을 읽고 "변수를 묶지 않으면 어떻게 클로저인가?"라고 생각했다. 하지만 지금은 언제 평가가 일어나는지 이해합니다. 감사합니다!
Jerome

예, 이것은 명백한 보편적 인 해결책입니다. 느리고 메모리 집약적이지만 모든 경우에 실제로 100 % 작동한다고 생각합니다.
Al Kepp

8

블록 범위 변수는 경고를 해결해야합니다.

foreach (var entry in entries)
{
   var en = entry; 
   var result = DoSomeAction(o => o.Action(en));
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.