List <T> .ForEach가 목록 수정을 허용하는 이유는 무엇입니까?


90

내가 사용하는 경우 :

var strings = new List<string> { "sample" };
foreach (string s in strings)
{
  Console.WriteLine(s);
  strings.Add(s + "!");
}

Add의는 foreach우리가 우리의 발 아래에서 양탄자를 당겨 때문에 나는, 논리적 생각하는 (열거 작업이 실행되지 않을 수 있습니다 컬렉션이 수정 된) InvalidOperationException이 발생합니다.

그러나 다음을 사용하는 경우 :

var strings = new List<string> { "sample" };
strings.ForEach(s =>
  {
    Console.WriteLine(s);
    strings.Add(s + "!");
  });

OutOfMemoryException이 발생할 때까지 루프를 실행하여 즉시 발을 쏜다.

난 항상 List.ForEach가 중 단지 래퍼라고 생각 이것은, 나에게 깜짝 온다 foreach또는 위해 for.
누구든지이 행동의 방법과 이유에 대한 설명이 있습니까?

( 끝없이 반복되는 Generic List에 대한 ForEach 루프에서 영감을 얻음 )


7
나는 동의한다. 이것은-모호합니다. Microsoft Connect에 게시하고 설명을 요청합니다.
TomTom

4
"List.ForEach는 단순히을위한 foreach또는에 대한 래퍼 일 뿐이라고 항상 생각했듯이 이것은 나에게 놀라운 일 입니다 for." 여전히 사용할 수 있습니다 for. for루프 에서 동일한 작업을 수행하고 결과적으로 동일한 OutOfMemoryException을 생성 할 수 있습니다.
Anthony Pegram

이것은 내 질문을 기반으로합니다 : stackoverflow.com/q/9311272/132239 , SWeko의 세부 사항에 대해 감사드립니다
Kasrak

답변:


68

때문입니다 ForEach열거를 사용하지 않는 방법은, 그것은을 가진 항목을 통해 루프 for루프 :

public void ForEach(Action<T> action)
{
    if (action == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }
    for (int i = 0; i < this._size; i++)
    {
        action(this._items[i]);
    }
}

(JustDecompile로 얻은 코드)

열거자를 사용하지 않기 때문에 목록이 변경되었는지 확인 for하지 않으며 _size반복 할 때마다 증가 하므로 루프 의 끝 조건에 도달하지 않습니다 .


예,하지만 어떻게 _size계산됩니까? 미리 계산 된 경우 내 예제에서 한 번만 실행해야합니다. 어쩐지 분명히 새로워졌습니다.
SWeko 2012

7
Add 메소드에서 새로 고침-> this._items [this._size ++] = item;
Fabio

1
@SWeko, 계산되지 않고 항목이 추가되거나 제거 될 때마다 업데이트됩니다.
Thomas Levesque 2012

1
목록 자체를 변경하는 작업에 대해 업데이트되므로 이러한 종류의 시나리오를 감지 할 수 있는 _version개인 변수가 List<T>있습니다.
SWeko 2012

먼저 크기 (int theSize = this._size)를 얻은 다음 for 루프 내에서 사용하여 예외를 피할 수 있습니까?
Lazlow

14

List<T>.ForEachfor내부를 통해 구현 되므로 열거자를 사용하지 않고 컬렉션을 수정할 수 있습니다.


6

List 클래스에 연결된 ForEach는 내부적으로 내부 멤버에 직접 연결된 for 루프를 사용하기 때문에 .NET 프레임 워크의 소스 코드를 다운로드하여 확인할 수 있습니다.

http://referencesource.microsoft.com/netframework.aspx

foreach 루프가 무엇보다도 컴파일러 최적화이지만 관찰자로서 컬렉션에 대해 작동해야하는 경우-컬렉션이 수정되면 예외가 발생합니다.


그리고 @Thomas 게시물의 새로 고침 방법에 대한 의견에 대답하기 위해 add가 호출되면 내부 구성원이 새로 고쳐 지므로 변경 사항을 따라갈 수 있습니다. 현재 항목보다 작은 인덱스에서 삽입을 수행하는 경우 해당 항목이 이미 해당 항목을 지나서 반복되었으므로 해당 항목에 대해 작업을 수행하지 않습니다. 그러나 마지막에 추가하기 때문에 작동합니다.
Mike Perrenoud

1
예, Add줄을 변경하면 strings.Insert(0, s + "!")'샘플'이 출력됩니다. 이것이 문서에서 전혀 언급되지 않은 것이 이상합니다.
SWeko 2012

글쎄요, Microsoft는 문서에있는 모든 경고를 제공하는 것이 거의 불가능하다는 것을 깨달았으므로 이제 소스 코드를 제공합니다. 솔직히 더 나은 솔루션을 찾았지만 내가 찾은 유일한 문제는 WF와 같은 제품이 빠르게 업데이트되지 않는다는 것입니다. 4.x WF 소스 코드는 여전히 사용할 수 없습니다.
Mike Perrenoud 2012

4

우리는이 문제에 대해 알고 있으며 원래 작성되었을 때 감독이었습니다. 안타깝게도 이전에 작동하던 코드가 실행되지 않도록 할 수 있으므로 변경할 수 없습니다.

        var list = new List<string>();
        list.Add("Foo");
        list.Add("Bar");

        list.ForEach((item) => 
        { 
            if(item=="Foo") 
                list.Remove(item); 
        });

Eric Lippert가 지적 했듯이이 방법 자체의 유용성은 의심 스럽기 때문에 Metro 스타일 앱 (예 : Windows 8 앱) 용 .NET에는 포함하지 않았습니다.

David Kean (BCL 팀)


1
나는 이것이 큰 나쁜 변화가 될 것이라고 생각하지만 그럼에도 불구하고 그것은 분명하지 않은 방식으로 실패 할 수 있으며 결코 좋은 일이 아닙니다. ForEach 메서드를 사용하는 것이 단순 (또는 원래 목록의 맹 글링이 필요하지 않은 경우 foreach)보다 우수한 시나리오를 볼 수 없습니다.
SWeko
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.