У меня классический случай попытки удалить элемент из коллекции при его перечислении в цикле:
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.
}
В начале итерации после того, как происходит изменение, выдается an InvalidOperationException
, потому что перечислителям не нравится, когда изменяется базовая коллекция.
Мне нужно внести изменения в коллекцию во время итерации. Есть много шаблонов, которые можно использовать, чтобы избежать этого , но, похоже, ни один из них не имеет хорошего решения:
Не удаляйте внутри этого цикла, вместо этого сохраняйте отдельный «Список удаления», который вы обрабатываете после основного цикла.
Обычно это хорошее решение, но в моем случае мне нужно, чтобы элемент исчез немедленно, так как «ожидание» до тех пор, пока основной цикл действительно удалить элемент, изменит логический поток моего кода.
Вместо того, чтобы удалять элемент, просто установите для него флажок и отметьте его как неактивный. Затем добавьте функциональность шаблона 1, чтобы очистить список.
Это будет работать для всех моих потребностей, но это означает , что много коды придется изменить для того , чтобы проверить Недеятельную пометку каждый раз , когда пункт доступ. На мой взгляд, это слишком много администрирования.
Каким-то образом включить идеи паттерна 2 в класс, производный от
List<T>
. Этот суперсписок будет обрабатывать флаг неактивности, удаление объектов постфактум, а также не будет предоставлять элементы, отмеченные как неактивные, для потребителей перечисления. По сути, он просто инкапсулирует все идеи шаблона 2 (а затем и шаблона 1).Существует ли такой класс? У кого-нибудь есть код для этого? Или есть способ лучше?
Мне сказали, что доступ
myIntCollection.ToArray()
вместоmyIntCollection
решит проблему и позволит мне удалить внутри цикла.Мне это кажется плохим шаблоном проектирования, или, может быть, это нормально?
Детали:
В списке будет много пунктов, и я удалю только некоторые из них.
Внутри цикла я буду выполнять всевозможные процессы, добавлять, удалять и т. Д., Поэтому решение должно быть достаточно общим.
Элемент, который мне нужно удалить, может не быть текущим элементом в цикле. Например, я могу находиться на элементе 10 цикла из 30 элементов, и мне нужно удалить элемент 6 или элемент 26. Из-за этого переход назад по массиву больше не будет работать. ; o (
источник
Ответы:
Лучшим решением обычно является использование
RemoveAll()
метода:myList.RemoveAll(x => x.SomeProp == "SomeValue");
Или, если вам нужно удалить определенные элементы:
MyListType[] elems = new[] { elem1, elem2 }; myList.RemoveAll(x => elems.Contains(x));
Это, конечно, предполагает, что ваш цикл предназначен исключительно для удаления. Если вам действительно нужна дополнительная обработка, то лучшим методом обычно является использование цикла
for
илиwhile
, поскольку в этом случае вы не используете перечислитель:for (int i = myList.Count - 1; i >= 0; i--) { // Do processing here, then... if (shouldRemoveCondition) { myList.RemoveAt(i); } }
Переход назад гарантирует, что вы не пропустите ни одного элемента.
Ответ на редактирование :
Если вы собираетесь удалить кажущиеся произвольными элементы, самый простой способ - просто отслеживать элементы, которые вы хотите удалить, а затем удалять их все сразу после этого. Что-то вроде этого:
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));
источник
Если вы должны как перечислить, так
List<T>
и удалить из него, я предлагаю просто использоватьwhile
цикл вместоforeach
var index = 0; while (index < myList.Count) { if (someCondition(myList[index])) { myList.RemoveAt(index); } else { index++; } }
источник
Я знаю, что этот пост старый, но я подумал, что поделюсь тем, что сработало для меня.
Создайте копию списка для перечисления, а затем в каждом цикле вы можете обработать скопированные значения и удалить / добавить / что-то еще с исходным списком.
private void ProcessAndRemove(IList<Item> list) { foreach (var item in list.ToList()) { if (item.DeterminingFactor > 10) { list.Remove(item); } } }
источник
Когда вам нужно выполнить итерацию по списку и вы можете изменить его во время цикла, вам лучше использовать цикл for:
for (int i = 0; i < myIntCollection.Count; i++) { if (myIntCollection[i] == 42) { myIntCollection.Remove(i); i--; } }
Конечно, вы должны быть осторожны, например, я уменьшаю
i
каждый раз, когда элемент удаляется, иначе мы пропустим записи (альтернатива - вернуться назад по списку).Если у вас есть Linq, вы должны просто использовать,
RemoveAll
как предлагает dlev.источник
--i
.По мере того, как вы перечисляете список, добавляйте тот, который хотите СОХРАНИТЬ, в новый список. После этого назначьте новый список
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;
источник
Добавим вам код:
List<int> myIntCollection=new List<int>(); myIntCollection.Add(42); myIntCollection.Add(12); myIntCollection.Add(96); myIntCollection.Add(25);
Если вы хотите изменить список, находясь в foreach, вы должны ввести
.ToList()
foreach(int i in myIntCollection.ToList()) { if (i == 42) myIntCollection.Remove(96); if (i == 25) myIntCollection.Remove(42); }
источник
Для тех, кто может помочь, я написал этот метод расширения, чтобы удалить элементы, соответствующие предикату, и вернуть список удаленных элементов.
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; }
источник
Как насчет
int[] tmp = new int[myIntCollection.Count ()]; myIntCollection.CopyTo(tmp); foreach(int i in tmp) { myIntCollection.Remove(42); //The error is no longer here. }
источник
foreach (int i in myIntCollection.ToArray()) { myIntCollection.Remove(42); }
для любого перечислимого, и, вList<T>
частности, он поддерживает этот метод даже в .NET 2.0.Если вас интересует высокая производительность, вы можете использовать два списка. Следующее минимизирует сборку мусора, максимизирует локальность памяти и никогда не удаляет элемент из списка, что очень неэффективно, если это не последний элемент.
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; }
источник
Просто подумал, что поделюсь своим решением аналогичной проблемы, когда мне нужно было удалить элементы из списка во время их обработки.
Таким образом, в основном "foreach" удаляет элемент из списка после его итерации.
Мой тест:
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));
Я решил эту проблему с помощью метода расширения «PopForEach», который выполнит действие и затем удалит элемент из списка.
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); } } }
Надеюсь, это может быть полезно любому.
источник