Saya baru-baru ini berlari ke operasi umum yang tidak valid ini Collection was modified
di 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?
You consume an iterator from client code by using a For Each…Next (Visual Basic) or foreach (C#) statement
barisIterator
(berfungsi seperti yang Anda gambarkan) danListIterator
(berfungsi sesuai keinginan). Ini akan menunjukkan bahwa untuk menyediakan fungsionalitas iterator pada berbagai jenis koleksi dan implementasi,Iterator
sangat membatasi (apa yang terjadi jika Anda menghapus simpul di pohon seimbang? Apakahnext()
masuk akal lagi), denganListIterator
menjadi lebih kuat karena memiliki lebih sedikit gunakan kasing.Jawaban:
Jawaban singkatnya: tidak
Sederhananya, Anda menghasilkan perilaku yang tidak terdefinisi , ketika Anda mengulangi koleksi dan memodifikasinya secara bersamaan. Pikirkan menghapus satu
next
elemen secara berurutan. Apa yang akan terjadi, jikaMoveNext()
dipanggil?Sumber: MSDN
Ngomong-ngomong, Anda bisa mempersingkat
RemoveAllBooks
menjadi 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:
sumber
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.
sumber