Как удалить объекты из коллекции Enumerable в цикле [дубликат]
-
03-07-2019 - |
Вопрос
На этот вопрос уже есть ответ здесь:
Дублировать
Изменение коллекции при ее переборе
Есть ли у кого-нибудь хороший шаблон, позволяющий мне обойти невозможность удалять объекты во время цикла через перечислимую коллекцию (например, IList или KeyValuePairs в словаре)
Например, следующее не удается, поскольку оно изменяет список, перечисляемый во время foreach.
foreach (MyObject myObject in MyListOfMyObjects)
{
if (condition) MyListOfMyObjects.Remove(myObject);
}
Раньше я использовал два метода.
Я заменил foreach обратным циклом for (чтобы не менять индексы, которые я просматриваю, если удаляю объект).
Я также попытался сохранить новую коллекцию объектов для удаления внутри цикла, затем просмотрел эту коллекцию и удалил объекты из исходной коллекции.
Они работают нормально, но ни чувствует здорово, и мне интересно, придумал ли кто-нибудь более элегантный решение проблемы
Решение
есть полезная вещь List<T>.RemoveAll(Predicate<T> match)
метод, который, я думаю, предназначен для этого: http://msdn.microsoft.com/en-us/library/wdka673a.aspx
Другие советы
Это довольно просто, но когда я планирую удалять элементы из IEnumerable/IList, я обычно просто делаю копию:
foreach (MyObject myObject in new List<MyObject>(MyListOfMyObjects))
{
if (condition) MyListOfMyObjects.Remove(myObject);
}
Это не самый эффективный способ сделать это, но его легко читать.Преждевременная оптимизация и все такое.
Сделайте обратное, создав новый список:
List myFilteredList = new List();
foreach (MyObject myObject in myListOfMyObjects)
{
if (!condition) myFilteredList.Add(myObject);
}
Затем используйте новый список там, где он вам нужен.
Вы также можете легко использовать выражение LINQ, опять же, инвертируя условие.Это имеет дополнительное преимущество: не создается новая структура, но есть и подводные камни, связанные с ленивым перечислением:
var myFilteredList = from myObject in myListOfMyObjects
where !condition
select myObject;
Однако, если вам действительно нужно удалить элементы из списка, я обычно использую подход «создать новый список, затем повторить и удалить».
Я только что наткнулся на этот пост и решил поделиться.
void RemoveAll(object condition)
{
bool found = false;
foreach(object thisObject in objects)
{
if (condition)
{
objects.Remove(thisObject);
found = true;
break; //exit loop
}
}
// Call again recursively
if (found) RemoveAll(condition);
}
Мне не нравится идея обратного цикла for, поскольку она работает только с определенными структурами данных.
В общем, я бы использовал второй метод и собирал элементы, подлежащие удалению, в отдельную коллекцию «подлежащих удалению».Если удаление может привести к тому, что существующие итерации станут недействительными (как это произойдет, например, с любой сбалансированной коллекцией деревьев), то я не вижу способа обойти это.
Единственный другой метод, который я иногда использовал, — это перезапуск всей итерации, когда вы найдете первый элемент, который нужно удалить.Если вы справитесь и не найдете элементов для удаления, функция будет завершена.Это неэффективно, но иногда необходимо, если удаление одного элемента из коллекции может изменить набор элементов, которые необходимо удалить.
У меня есть словарь, и я хочу удалить все значения.Когда каждое значение удаляется, оно удаляется из словаря, что создает обсуждаемую вами проблему.Я сделал следующее:
foreach (var o in dictionary.Values.ToList())
{
o.Dispose();
}
Я понимаю, что, возможно, это уже мертво, но я всегда делаю это так:
foreach (MyObject myObject в MyListOfMyObjects)
{
если (условие) MyListOfMyObjects.Remove(myObject);
перерыв;
}
Объект удаляется, а затем цикл завершается, альт!