.NET - Удалить из списка <T>внутри цикла 'foreach' - PullRequest
46 голосов
/ 07 мая 2009

У меня есть код, который я хочу выглядеть следующим образом:

List<Type> Os;

...

foreach (Type o in Os)
    if (o.cond)
        return;  // Quitting early is important for my case!
    else
        Os.Remove(o);

... // Other code

Это не работает, потому что вы не можете удалить из списка, когда находитесь внутри цикла foreach над этим списком:

Есть ли общий способ решения проблемы?

При необходимости я могу переключиться на другой тип.

Вариант 2:

List<Type> Os;

...

while (Os.Count != 0)
     if (Os[0].cond)
         return;
     else
         Os.RemoveAt(0);

... // Other code

Ужасно, но это должно сработать.

Ответы [ 17 ]

58 голосов
/ 07 мая 2009

Вы можете перебирать список в обратном порядке:

for (int i = myList.Count - 1; i >= 0; i--)
{
    if (whatever) myList.RemoveAt(i);
}

В ответ на ваш комментарий о желании выйти, когда вы обнаружите элемент, который НЕ удаляете, лучшим решением будет просто использование цикла while.

55 голосов
/ 07 мая 2009

Вы никогда не должны удалять что-либо из коллекции, которую вы повторяете внутри цикла foreach. Это в основном как пилить ветку, на которой ты сидишь.

Используй альтернативу. Это путь.

30 голосов
/ 07 мая 2009

Вам действительно нужно сделать это в цикле foreach?

Это приведет к тем же результатам, что и ваши примеры, т.е. удалит все элементы из списка вплоть до первого элемента, соответствующего условию (или удалит все элементы, если ни один из них не соответствует условию).

int index = Os.FindIndex(x => x.cond);

if (index > 0)
    Os.RemoveRange(0, index);
else if (index == -1)
    Os.Clear();
15 голосов
/ 07 мая 2009

Я программист на Java, но что-то вроде этого работает:

List<Type> Os;
List<Type> Temp;
...
foreach (Type o in Os)
    if (o.cond)
        Temp.add(o);
Os.removeAll(Temp);  
13 голосов
/ 07 мая 2009

У меня только что была эта проблема с моей библиотекой анализа. Я попробовал это:

for (int i = 0; i < list.Count; i++)
{                
   if (/*condition*/)
   {
       list.RemoveAt(i);
       i--;
   }
}

Это довольно просто, но я не думал ни о каком переломном моменте.

12 голосов
/ 21 февраля 2012

Вот ПРОСТОЕ РЕШЕНИЕ с самым простым ПОЧЕМУ

ПРОБЛЕМА:

Как правило, мы удаляем из исходного списка, это создает проблему поддержания счетчика списка и местоположения итератора.

List<Type> Os = ....;
Os.ForEach(
    delegate(Type o) {
        if(!o.cond) Os.Remove(o);
    }
);

РЕШЕНИЕ - LINQ.ForEach:

Обратите внимание, все, что я добавил, было ToList(). Это создает новый список, для которого вы выполняете ForEach, поэтому вы можете удалить свой первоначальный список, продолжая при этом перебирать весь список.

List<Type> Os = ....;
Os<b>.ToList()</b>.ForEach(
    delegate(Type o) {
        if(!o.cond) Os.Remove(o);
    }
);

РЕШЕНИЕ - Обычное foreach:

Этот метод также работает для регулярных foreach операторов.

List<Type> Os = ....;
foreach(Type o in Os<b>.ToList()</b>) {
  if(!o.cond) Os.Remove(o);
}

Обратите внимание, что это решение не будет работать, если ваш исходный список содержит struct элемент.

11 голосов
/ 30 августа 2010

Я знаю, что вы просили что-то еще, но если вы хотите условно удалить группу элементов, вы можете использовать лямбда-выражение:

Os.RemoveAll(o => !o.cond);
9 голосов
/ 07 мая 2009
 Os.RemoveAll(delegate(int x) { return /// });
4 голосов
/ 07 мая 2009

Я бы попробовал найти индекс первого элемента, который не удовлетворяет предикату, и выполнить для него RemoveRange (0, index). Если ничего другого, должно быть меньше Удалить звонки.

3 голосов
/ 07 мая 2009

Обновление: добавлено для полноты

Как ответили несколько человек, вы не должны изменять коллекцию при ее итерации с помощью GetEnumerator () (пример foreach). Фреймворк мешает вам сделать это, выдав исключение. Общей формулировкой этого является итерация «вручную» с for (см. Другие ответы). Будьте осторожны с индексом, чтобы не пропускать элементы и не переоценивать один и тот же дважды (используя i-- или итерацию в обратном направлении).

Однако для вашего конкретного случая мы можем оптимизировать операцию (и) удаления ... оригинальный ответ ниже.


Если вы хотите удалить все элементы, пока один из них не удовлетворяет заданному условию (это то, что делает ваш код), вы можете сделать это:

bool exitCondition;

while(list.Count > 0 && !(exitCondition = list[0].Condition))
   list.RemoveAt(0);

Или, если вы хотите использовать одну операцию удаления:

SomeType exitCondition;
int index = list.FindIndex(i => i.Condition);

if(index < 0)
    list.Clear();
else
{
    exitCondition = list[0].State;
    list.RemoveRange(0, count);
}

Примечание: поскольку я предполагаю, что item.Condition равно bool, я использую item.State для сохранения условия выхода.

Обновление: добавлена ​​проверка границ и сохранение условия выхода в обоих примерах

...