Saya memiliki kasus klasik mencoba menghapus item dari koleksi sambil menghitungnya dalam satu lingkaran:
List<int> myIntCollection = new List<int>();
myIntCollection.Add(42);
myIntCollection.Add(12);
myIntCollection.Add(96);
myIntCollection.Add(25);
foreach (int i in myIntCollection)
{
if (i == 42)
myIntCollection.Remove(96); // The error is here.
if (i == 25)
myIntCollection.Remove(42); // The error is here.
}
Pada awal iterasi setelah perubahan terjadi, an InvalidOperationException
dilemparkan, karena enumerator tidak suka ketika koleksi yang mendasarinya berubah.
Saya perlu melakukan perubahan pada koleksi saat melakukan iterasi. Ada banyak pola yang dapat digunakan untuk menghindari hal ini , tetapi tidak satupun dari pola tersebut memiliki solusi yang baik:
Jangan hapus di dalam loop ini, sebagai gantinya simpan "Hapus Daftar" terpisah, yang Anda proses setelah loop utama.
Ini biasanya merupakan solusi yang baik, tetapi dalam kasus saya, saya perlu item untuk segera pergi sebagai "menunggu" sampai setelah loop utama untuk benar-benar menghapus item mengubah aliran logika kode saya.
Daripada menghapus item, cukup setel bendera pada item dan tandai sebagai tidak aktif. Kemudian tambahkan fungsionalitas pola 1 untuk membersihkan daftar.
Ini akan berfungsi untuk semua kebutuhan saya, tetapi itu berarti bahwa banyak kode harus diubah untuk memeriksa bendera tidak aktif setiap kali item diakses. Ini terlalu banyak administrasi untuk saya sukai.
Entah bagaimana, gabungkan ide-ide pola 2 dalam kelas yang diturunkan dari
List<T>
. Superlist ini akan menangani flag tidak aktif, penghapusan objek setelah fakta dan juga tidak akan mengekspos item yang ditandai sebagai tidak aktif untuk konsumen pencacahan. Pada dasarnya, ini hanya merangkum semua ide dari pola 2 (dan selanjutnya pola 1).Apakah kelas seperti ini ada? Apakah ada yang punya kode untuk ini? Atau apakah ada cara yang lebih baik?
Saya telah diberi tahu bahwa mengakses
myIntCollection.ToArray()
alih-alihmyIntCollection
akan menyelesaikan masalah dan memungkinkan saya menghapus di dalam loop.Ini sepertinya pola desain yang buruk bagi saya, atau mungkin tidak masalah?
Rincian:
Daftar akan berisi banyak item dan saya hanya akan menghapus beberapa dari mereka.
Di dalam loop, saya akan melakukan semua jenis proses, menambahkan, menghapus, dll., Jadi solusinya harus cukup umum.
Item yang perlu saya hapus mungkin bukan item saat ini dalam loop. Misalnya, saya mungkin berada di item 10 dari loop 30 item dan perlu menghapus item 6 atau item 26. Berjalan mundur melalui array tidak akan berfungsi lagi karena ini. ;Hai(
sumber
Jawaban:
Solusi terbaik biasanya menggunakan
RemoveAll()
metode:myList.RemoveAll(x => x.SomeProp == "SomeValue");
Atau, jika Anda ingin elemen tertentu dihapus:
MyListType[] elems = new[] { elem1, elem2 }; myList.RemoveAll(x => elems.Contains(x));
Ini mengasumsikan bahwa loop Anda semata-mata ditujukan untuk tujuan penghapusan, tentu saja. Jika Anda memang perlu pemrosesan tambahan, maka metode terbaik biasanya menggunakan
for
atauwhile
loop, karena Anda tidak menggunakan enumerator:for (int i = myList.Count - 1; i >= 0; i--) { // Do processing here, then... if (shouldRemoveCondition) { myList.RemoveAt(i); } }
Mundur memastikan bahwa Anda tidak melewatkan elemen apa pun.
Tanggapan untuk Edit :
Jika Anda akan menghapus elemen yang tampaknya sewenang-wenang, metode termudah mungkin adalah dengan melacak elemen yang ingin Anda hapus, dan kemudian menghapus semuanya sekaligus setelahnya. Sesuatu seperti ini:
List<int> toRemove = new List<int>(); foreach (var elem in myList) { // Do some stuff // Check for removal if (needToRemoveAnElement) { toRemove.Add(elem); } } // Remove everything here myList.RemoveAll(x => toRemove.Contains(x));
sumber
Jika Anda berdua harus menghitung a
List<T>
dan menghapusnya maka saya sarankan cukup menggunakanwhile
loop daripada aforeach
var index = 0; while (index < myList.Count) { if (someCondition(myList[index])) { myList.RemoveAt(index); } else { index++; } }
sumber
Saya tahu posting ini sudah lama, tetapi saya pikir saya akan membagikan apa yang berhasil untuk saya.
Buat salinan daftar untuk pencacahan, dan kemudian di untuk setiap loop, Anda dapat memproses pada nilai yang disalin, dan menghapus / menambah / apa pun dengan daftar sumber.
private void ProcessAndRemove(IList<Item> list) { foreach (var item in list.ToList()) { if (item.DeterminingFactor > 10) { list.Remove(item); } } }
sumber
Saat Anda perlu mengulang daftar dan mungkin memodifikasinya selama pengulangan, lebih baik Anda menggunakan perulangan for:
for (int i = 0; i < myIntCollection.Count; i++) { if (myIntCollection[i] == 42) { myIntCollection.Remove(i); i--; } }
Tentu saja Anda harus berhati-hati, misalnya saya mengurangi
i
setiap kali item dihapus karena jika tidak kami akan melewatkan entri (alternatifnya adalah mundur melalui daftar).Jika Anda memiliki Linq maka Anda harus menggunakan
RemoveAll
seperti yang disarankan dlev.sumber
--i
.Saat Anda menghitung daftar, tambahkan yang ingin Anda TETAP ke daftar baru. Setelah itu, tetapkan daftar baru ke
myIntCollection
List<int> myIntCollection=new List<int>(); myIntCollection.Add(42); List<int> newCollection=new List<int>(myIntCollection.Count); foreach(int i in myIntCollection) { if (i want to delete this) /// else newCollection.Add(i); } myIntCollection = newCollection;
sumber
Mari tambahkan kode Anda:
List<int> myIntCollection=new List<int>(); myIntCollection.Add(42); myIntCollection.Add(12); myIntCollection.Add(96); myIntCollection.Add(25);
Jika Anda ingin mengubah daftar saat Anda berada di foreach, Anda harus mengetik
.ToList()
foreach(int i in myIntCollection.ToList()) { if (i == 42) myIntCollection.Remove(96); if (i == 25) myIntCollection.Remove(42); }
sumber
Bagi mereka yang mungkin membantu, saya menulis metode Ekstensi ini untuk menghapus item yang cocok dengan predikat dan mengembalikan daftar item yang dihapus.
public static IList<T> RemoveAllKeepRemoved<T>(this IList<T> source, Predicate<T> predicate) { IList<T> removed = new List<T>(); for (int i = source.Count - 1; i >= 0; i--) { T item = source[i]; if (predicate(item)) { removed.Add(item); source.RemoveAt(i); } } return removed; }
sumber
Bagaimana tentang
int[] tmp = new int[myIntCollection.Count ()]; myIntCollection.CopyTo(tmp); foreach(int i in tmp) { myIntCollection.Remove(42); //The error is no longer here. }
sumber
foreach (int i in myIntCollection.ToArray()) { myIntCollection.Remove(42); }
untuk semua enumerable, danList<T>
secara khusus mendukung metode ini bahkan dalam .NET 2.0.Jika Anda tertarik dengan kinerja tinggi, Anda dapat menggunakan dua daftar. Hal berikut meminimalkan pengumpulan sampah, memaksimalkan lokalitas memori dan tidak pernah benar-benar menghapus item dari daftar, yang sangat tidak efisien jika itu bukan item terakhir.
private void RemoveItems() { _newList.Clear(); foreach (var item in _list) { item.Process(); if (!item.NeedsRemoving()) _newList.Add(item); } var swap = _list; _list = _newList; _newList = swap; }
sumber
Baru saja membayangkan saya akan membagikan solusi saya untuk masalah serupa di mana saya perlu menghapus item dari daftar saat memprosesnya.
Jadi pada dasarnya "foreach" yang akan menghapus item dari daftar setelah iterasi.
Tes saya:
var list = new List<TempLoopDto>(); list.Add(new TempLoopDto("Test1")); list.Add(new TempLoopDto("Test2")); list.Add(new TempLoopDto("Test3")); list.Add(new TempLoopDto("Test4")); list.PopForEach((item) => { Console.WriteLine($"Process {item.Name}"); }); Assert.That(list.Count, Is.EqualTo(0));
Saya menyelesaikan ini dengan metode ekstensi "PopForEach" yang akan melakukan tindakan dan kemudian menghapus item dari daftar.
public static class ListExtensions { public static void PopForEach<T>(this List<T> list, Action<T> action) { var index = 0; while (index < list.Count) { action(list[index]); list.RemoveAt(index); } } }
Semoga ini bisa membantu siapa saja.
sumber