Подтвердить что ты не робот

.NET - удалить из списка <T> внутри цикла foreach

У меня есть код, который должен выглядеть так:

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

Уродливый, но он должен работать.

4b9b3361

Ответ 1

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

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

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

if (index > 0)
    Os.RemoveRange(0, index);
else if (index == -1)
    Os.Clear();

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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

List<Type> Os;
List<Type> Temp;
...
foreach (Type o in Os)
    if (o.cond)
        Temp.add(o);
Os.removeAll(Temp);  

Ответ 5

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

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

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

Ответ 6

Вот EASIEST SOLUTION с самым простым ПОЧЕМУ

Проблема:

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

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

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

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

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

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

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

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

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

Ответ 7

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

Os.RemoveAll(o => !o.cond);

Ответ 8

 Os.RemoveAll(delegate(int x) { return /// });

Ответ 9

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

Ответ 10

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

Как уже было сказано, вы не должны изменять коллекцию, итерации ее с помощью 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 для сохранения условия выхода.

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

Ответ 12

Если вы знаете, что ваш список не очень большой, вы можете использовать

foreach (Type o in new List<Type>(Os))
    ....

который создаст временный дубликат списка. Ваш вызов remove() не будет мешать итератору.

Ответ 13

Посмотрите Enumerable.SkipWhile()

Enumerable.SkipWhile( x => condition).ToList()

Как правило, не мутирует список, делает жизнь намного проще.:)

Ответ 14

вы можете сделать это с помощью linq

MyList = MyList.Where(x=>(someCondition(x)==true)).ToList()

Ответ 15

Решение Anzurio, пожалуй, самое простое, но здесь еще одно чистое, если вы не возражаете добавить кучу интерфейсов/классов в свою утилиту.

Вы можете написать это так:

List<Type> Os;
...
var en = Os.GetRemovableEnumerator();
while (en.MoveNext())
{
    if (en.Current.Cond)
        en.Remove();
}

Поместите следующую инфраструктуру, вдохновленную Java Iterator<T>.remove в вашу библиотеку:

static class Extensions
{
    public static IRemovableEnumerator<T> GetRemovableEnumerator<T>(this IList<T> l)
    {
        return new ListRemovableEnumerator<T>(l);
    }
}

interface IRemovableEnumerator<T> : IEnumerator<T>
{
    void Remove();
}

class ListRemovableEnumerator<T> : IRemovableEnumerator<T>
{
    private readonly IList<T> _list;
    private int _count;
    private int _index;
    public ListRemovableEnumerator(IList<T> list)
    {
        _list = list;
        _count = list.Count;
        _index = -1;
    }

    private void ThrowOnModification()
    {
        if (_list.Count != _count)
            throw new InvalidOperationException("List was modified after creation of enumerator");
    }
    public void Dispose()
    {
    }

    public bool MoveNext()
    {
        ThrowOnModification();
        if (_index + 1 == _count)
            return false;
        _index++;
        return true;
    }

    public void Reset()
    {
        ThrowOnModification();
        _index = -1;
    }

    object IEnumerator.Current
    {
        get { return Current; }
    }

    public T Current
    {
        get { return _list[_index]; }
    }

    public void Remove()
    {
        ThrowOnModification();
        _list.RemoveAt(_index);
        _index--;
        _count--;
    }
}

Ответ 16

У меня была одна и та же проблема и она была решена с помощью следующего:

foreach (Type o in (new List(Os))) { if (something) Os.Remove(o); }

Итерирует через копию списка и удаляет из исходного списка.

Ответ 17

Добавьте элемент для удаления из списка, а затем удалите эти элементы с помощью RemoveAll:

List<Type> Os;
List<Type> OsToRemove=new List<Type>();
...
foreach (Type o in Os){
    if (o.cond)
        return;
    else
        OsToRemove.Add(o);
}
Os.RemoveAll(o => OsToRemove.Contains(o));