Apakah membuat Daftar baru untuk memodifikasi koleksi untuk setiap loop cacat desain?


13

Saya baru-baru ini berlari ke operasi umum yang tidak valid ini Collection was modifieddi C #, dan sementara saya memahaminya sepenuhnya, tampaknya menjadi masalah umum (google, sekitar 300k hasil!). Tetapi tampaknya juga merupakan hal yang logis dan mudah untuk memodifikasi daftar saat Anda menjalaninya.

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"));
    }
}

Beberapa orang akan membuat daftar lain untuk diulangi, tetapi ini hanya menghindari masalah. Apa solusi yang sebenarnya, atau apa masalah desain yang sebenarnya di sini? Kita semua tampaknya ingin melakukan ini, tetapi apakah ini menunjukkan cacat desain?


Anda dapat menggunakan iterator atau menghapus dari akhir daftar.
ElDuderino

@ElDuderino msdn.microsoft.com/en-us/library/dscyy5s0.aspx . Jangan lewatkan You consume an iterator from client code by using a For Each…Next (Visual Basic) or foreach (C#) statementbaris
SJuan76

Di Jawa ada Iterator(berfungsi seperti yang Anda gambarkan) dan ListIterator(berfungsi sesuai keinginan). Ini akan menunjukkan bahwa untuk menyediakan fungsionalitas iterator pada berbagai jenis koleksi dan implementasi, Iteratorsangat membatasi (apa yang terjadi jika Anda menghapus simpul di pohon seimbang? Apakah next()masuk akal lagi), dengan ListIteratormenjadi lebih kuat karena memiliki lebih sedikit gunakan kasing.
SJuan76

@ SJuan76 dalam bahasa lain masih ada lebih banyak jenis iterator, misalnya C ++ memiliki iterator maju (setara dengan Java Iterator), iterator dua arah (seperti ListIterator), iterator akses acak, input iterator (seperti Java Iterator yang tidak mendukung operasi opsional ) dan output iterators (yaitu hanya menulis, yang lebih bermanfaat daripada kedengarannya).
Jules

Jawaban:


9

Apakah membuat Daftar baru untuk memodifikasi koleksi untuk setiap loop cacat desain?

Jawaban singkatnya: tidak

Sederhananya, Anda menghasilkan perilaku yang tidak terdefinisi , ketika Anda mengulangi koleksi dan memodifikasinya secara bersamaan. Pikirkan menghapus satu nextelemen secara berurutan. Apa yang akan terjadi, jika MoveNext()dipanggil?

Enumerator tetap valid selama koleksinya tidak berubah. Jika perubahan dilakukan pada koleksi, seperti menambahkan, memodifikasi, atau menghapus elemen, enumerator tidak dapat dibatalkan dan perilakunya tidak dapat ditentukan. Enumerator tidak memiliki akses eksklusif ke koleksi; Oleh karena itu, penghitungan melalui koleksi pada hakikatnya bukan prosedur yang aman.

Sumber: MSDN

Ngomong-ngomong, Anda bisa mempersingkat RemoveAllBooksmenjadi sederhanareturn new List<Book>()

Dan untuk menghapus buku, saya sarankan mengembalikan koleksi yang disaring:

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

Kemungkinan implementasi rak akan terlihat seperti:

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);
    }
}

Anda bertentangan dengan diri sendiri ketika Anda menjawab ya dan kemudian memberikan contoh yang juga membuat daftar baru. Selain itu saya setuju.
Esben Skov Pedersen

1
@EsbenSkovPedersen Anda benar: DI sedang berpikir untuk memodifikasi sebagai cacat ...
Thomas Junk

6

Membuat daftar baru untuk memodifikasinya adalah solusi yang baik untuk masalah iterator yang ada dari daftar tidak dapat melanjutkan setelah perubahan kecuali jika mereka tahu bagaimana daftar telah berubah.

Solusi lain adalah membuat modifikasi menggunakan iterator daripada melalui daftar antarmuka itu sendiri. Ini hanya berfungsi jika hanya ada satu iterator - iterator tidak menyelesaikan masalah untuk beberapa utas yang berulang pada daftar secara bersamaan, yang (selama mengakses data basi dapat diterima) membuat daftar baru tidak.

Solusi alternatif yang bisa diambil oleh pelaksana kerangka adalah membuat daftar melacak semua iteratornya dan memberi tahu mereka ketika terjadi perubahan. Namun, overhead dari pendekatan ini tinggi - agar dapat bekerja secara umum dengan banyak utas, semua operasi iterator perlu mengunci daftar (untuk memastikan tidak berubah saat operasi sedang berlangsung), yang akan membuat semua daftar operasi lambat. Juga akan ada overhead memori nontrivial, ditambah itu membutuhkan runtime untuk mendukung referensi lunak, dan IIRC versi pertama dari CLR tidak.

Dari perspektif yang berbeda, menyalin daftar untuk memodifikasinya memungkinkan Anda menggunakan LINQ untuk menentukan modifikasi, yang umumnya menghasilkan kode yang lebih jelas daripada secara langsung mengulangi daftar, IMO.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.