Как удалить объекты из коллекции Enumerable в цикле

Duplicate

Изменение коллекции при итерации через нее


У кого-нибудь хороший шаблон, позволяющий мне обойти невозможность удалить объекты, когда я прокручиваю перечислимую коллекцию (например, IList или KeyValuePairs в словаре)

Например, следующее не выполняется, поскольку оно изменяет список, который перечислит во время foreach

foreach (MyObject myObject in MyListOfMyObjects)
{
     if (condition) MyListOfMyObjects.Remove(myObject);
}

В прошлом я использовал два метода.

Я заменил foreach на обратный цикл for (чтобы не изменять любые индексы, которые я перебираю, если я удаляю объект).

Я также попытался сохранить новую коллекцию объектов для удаления внутри цикла, затем прокрутил эту коллекцию и удалил объекты из исходной коллекции.

Они работают отлично, но не чувствуют себя хорошо, и мне было интересно, если кто-нибудь придумает более элегантное решение проблемы

Ответы

Ответ 2

Это вроде простодушный, но когда я планирую удалить элементы из IEnumerable/IList, я обычно делаю копию:

foreach (MyObject myObject in new List<MyObject>(MyListOfMyObjects))
{
     if (condition) MyListOfMyObjects.Remove(myObject);
}

Это не самый эффективный способ сделать это, но его легко читать. Преждевременная оптимизация и все такое.

Ответ 3

Сделайте инверсию, создав новый список:

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;

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

Ответ 4

Я просто наткнулся на этот пост и думал, что поделюсь.

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);

}

Ответ 5

Мне не нравится обратная идея цикла, так как это работает только на определенных структурах данных.

В общем, я бы использовал второй метод и накапливал элементы, которые нужно удалить в отдельной коллекции "для удаления". Если удаление может привести к тому, что существующие итерации будут недействительными (как это происходит с любой сбалансированной коллекцией деревьев, например), я не вижу способа обойти это.

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

Ответ 6

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

foreach (MyObject myObject в MyListOfMyObjects)
{

if (condition) MyListOfMyObjects.Remove(myObject);

break;

}

Объект удаляется, а затем цикл выходит, альта!

Ответ 7

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

foreach (var o in dictionary.Values.ToList())
{
  o.Dispose();
}