루프마다 디자인 결함을 수정하기 위해 새 목록을 작성하고 있습니까?


14

나는 최근 Collection was modified에 C # 에서이 일반적인 무효 작업 에 부딪 쳤고 그것을 완전히 이해하는 동안 일반적인 문제 (Google, 약 300k 결과!) 인 것 같습니다. 그러나 목록을 검토하는 동안 목록을 수정하는 것은 논리적이고 간단한 것 같습니다.

List<Book> myBooks = new List<Book>();

public void RemoveAllBooks(){
    foreach(Book book in myBooks){
         RemoveBook(book);
    }
}

RemoveBook(Book book){
    if(myBooks.Contains(book)){
         myBooks.Remove(book);
         if(OnBookEvent != null)
            OnBookEvent(this, new EventArgs("Removed"));
    }
}

일부 사람들은 반복 할 다른 목록을 만들지 만 문제를 피하고 있습니다. 실제 솔루션은 무엇입니까? 여기서 실제 디자인 문제는 무엇입니까? 우리 모두 는 이것을 하기를 하지만 디자인 결함을 나타내는 것입니까?


반복자를 사용하거나 목록 끝에서 제거 할 수 있습니다.
ElDuderino

@ElDuderino msdn.microsoft.com/en-us/library/dscyy5s0.aspx . You consume an iterator from client code by using a For Each…Next (Visual Basic) or foreach (C#) statement라인을 놓치지 마세요
SJuan76

Java에는 Iterator(설명대로 ListIterator작동 ) 및 (원하는대로 작동)이 있습니다. 이것은 컬렉션 유형과 구현의 넓은 범위에 걸쳐 반복자 기능을 제공하기 위해 그 지점 것이다 Iterator(당신이 균형 잡힌 트리?합니까의에 노드를 삭제하면 무슨 일이 매우 제한적 next()으로, 더 이상 화장 감각) ListIterator적은 데 인해 더 강력한 것 사용 사례.
SJuan76

다른 언어로는 @ SJuan76 더 많은 반복자 유형이 있습니다. 예를 들어 C ++에는 순방향 반복기 (Java 반복기와 동일), 양방향 반복기 (ListIterator와 같은), 임의 액세스 반복기, 입력 반복기 (선택적 연산을 지원하지 않는 Java 반복기와 같은)가 있습니다. ) 및 출력 이터레이터 (즉, 쓰기 전용, 소리보다 유용합니다).
Jules

답변:


10

루프마다 디자인 결함을 수정하기 위해 새 목록을 작성하고 있습니까?

짧은 대답 : 아니오

간단히 말해서, 컬렉션을 반복하고 동시에 수정할 때 정의되지 않은 동작 을 생성 합니다. 순서대로 요소 를 삭제 하는 것을 고려 하십시오 next. 경우, 어떤 일이 일어날 것 MoveNext()이라고?

컬렉션은 변경되지 않은 한 열거자는 계속 유효합니다. 요소 추가, 수정 또는 삭제와 같이 컬렉션이 변경되면 열거자는 복구 할 수 없을 정도로 무효화되고 동작은 정의되지 않습니다. 열거자는 컬렉션에 단독으로 액세스 할 수 없습니다. 따라서 컬렉션을 통해 열거하는 것은 본질적으로 스레드 안전 절차가 아닙니다.

출처 : MSDN

그건 그렇고, 당신은 RemoveAllBooks단순히return new List<Book>()

그리고 책을 제거하려면 여과 된 컬렉션을 반환하는 것이 좋습니다.

return books.Where(x => x.Author != "Bob").ToList();

가능한 선반 구현은 다음과 같습니다.

public class Shelf
{
    List<Book> books=new List<Book> {
        new Book ("Paul"),
        new Book ("Peter")
    };

    public IEnumerable<Book> getAllBooks(){
        foreach(Book b in books){
            yield return b;
        }
    }

    public void RemovePetersBooks(){
        books= books.Where(x=>x.Author!="Peter").ToList();
    }

    public void EmptyShelf(){
        books = new List<Book> ();
    }

    public Shelf ()
    {
    }
}

public static void Main (string[] args)
{
    Shelf s = new Shelf ();
    foreach (Book b in s.getAllBooks()) {
        Console.WriteLine (b.Author);
    }
    s.RemovePetersBooks ();

    foreach (Book b in s.getAllBooks()) {
        Console.WriteLine (b.Author);
    }
    s.EmptyShelf ();
    foreach (Book b in s.getAllBooks()) {
        Console.WriteLine (b.Author);
    }
}

예라고 대답하면 모순 된 다음 새 목록을 만드는 예를 제공하십시오. 그 외에는 동의합니다.
Esben Skov Pedersen

1
@EsbenSkovPedersen 당신이 맞아 : DI는 결함으로 수정 생각했다 ...
Thomas Junk

6

목록을 수정하기 위해 새 목록을 작성하면 목록의 기존 반복자가 목록이 어떻게 변경되었는지 알지 않는 한 변경 후 안정적으로 계속할 수없는 문제에 대한 좋은 솔루션입니다.

또 다른 해결책은 목록 인터페이스 자체가 아닌 반복자를 사용하여 수정하는 것입니다. 이것은 하나의 반복자가있을 수있는 경우에만 작동합니다. 목록에 대해 여러 스레드가 동시에 반복되는 문제는 해결되지 않습니다 (부실 데이터에 액세스하는 것이 허용되는 한) 새 목록을 작성하면됩니다.

프레임 워크 구현자가 취할 수있는 대체 솔루션은 목록이 모든 반복자를 추적하고 변경이 발생하면이를 알리는 것입니다. 그러나이 접근법의 오버 헤드가 높기 때문에 일반적으로 여러 스레드에서 작동하려면 모든 반복기 작업이 목록을 잠 가야합니다 (작업이 진행되는 동안 변경되지 않도록) 모든 목록을 만듭니다 작업이 느려집니다. 사소한 메모리 오버 헤드가있을뿐만 아니라 런타임에 소프트 레퍼런스를 지원해야하며 IIRC의 첫 번째 버전에서는 CLR이 지원하지 않았습니다.

다른 관점에서 목록을 수정하여 수정하면 LINQ를 사용하여 수정을 지정할 수 있습니다. 일반적으로 목록 IMO를 직접 반복하는 것보다 더 명확한 코드가 생성됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.