Вопрос по list, enumeration, c#, foreach – Для тех, кому это может помочь, я написал этот метод Extension, чтобы удалить элементы, соответствующие предикату, и вернуть список удаленных элементов.

78

я есть классический случай попытки удалить элемент из коллекции при перечислении его в цикле:

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.
}

В начале итерации после измененияInvalidOperationException брошен, потому что перечислители не любят, когда базовая коллекция изменяется.

Мне нужно внести изменения в коллекцию во время итерации. Здесь очень многошаблоны, которые можно использовать, чтобы избежать этого, но ни у одного из них нет хорошего решения:

Не удаляйте внутри этого цикла, вместо этого держите отдельный «Удалить список», который вы обрабатываете после основного цикла.

Обычно это хорошее решение, но в моем случае мне нужно, чтобы элемент мгновенно исчезал как «ожидающий», пока после того, как основной цикл действительно удалит элемент, изменит логический поток моего кода.

Вместо удаления элемента просто установите флаг на элементе и отметьте его как неактивный. Затем добавьте функциональность шаблона 1, чтобы очистить список.

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

Каким-то образом включить идеи шаблона 2 в классе, который происходит отList<T>, Этот суперсписок будет обрабатывать флаг неактивности, удаление объектов после факта, а также не будет выставлять элементы, помеченные как неактивные для потребителей перечисления. По сути, он просто воплощает в себе все идеи шаблона 2 (и впоследствии шаблона 1).

Существует ли такой класс? У кого-нибудь есть код для этого? Или есть лучший способ?

Мне сказали, что доступmyIntCollection.ToArray() вместоmyIntCollection решит проблему и позволит мне удалить внутри цикла.

Мне кажется, это плохой шаблон дизайна, или, может быть, все в порядке?

Подробности:

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

Внутри цикла я буду выполнять все виды процессов, добавлять, удалять и т. Д., Поэтому решение должно быть достаточно общим.

Элемент, который мне нужно удалитьможет нет быть текущим элементом в цикле. Например, я могу быть на элементе 10 в цикле из 30 элементов и мне нужно удалить элемент 6 или элемент 26. Из-за этого больше не будет идти обратный ход по массиву. ; О (

Примечание: Списки тратят много времени (обычно O (N), где N - длина списка) на перемещение значений. Если эффективный произвольный доступ действительно необходим, можно достичь удалений в O (log N), используя сбалансированное двоичное дерево, содержащее количество узлов в поддереве, корнем которого он является. Это BST, чей ключ (индекс в последовательности) подразумевается. Palec
Возможная полезная информация для кого-то еще:Избегать сбора была изменена ошибка (инкапсуляция шаблона 1) George Duckett
Пожалуйста, смотрите ответ:stackoverflow.com/questions/7193294/... Dabbas

Ваш Ответ

9   ответов
182

Лучшее решение обычно заключается в использовании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));
Что касается вашего ответа: мне нужно удалить элементы немедленно во время обработки этого элемента, а не после того, как весь цикл был обработан. Решение, которое я использую, состоит в том, чтобы NULL любые элементы, которые я хочу удалить, и удалить их впоследствии. Это не идеальное решение, так как я должен проверять NULL повсюду, но оно работает. John Stock
-1

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

    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;
    }
4

добавьте тот, который вы хотите сохранить, в новый список. После этого назначьте новый список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;
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;
}
8

лучше использовать цикл for:

for (int i = 0; i < myIntCollection.Count; i++)
{
    if (myIntCollection[i] == 42)
    {
        myIntCollection.Remove(i);
        i--;
    }
}

Конечно, вы должны быть осторожны, например, я снимаюi всякий раз, когда элемент удаляется как-либо иначе, мы пропускаем записи (альтернатива состоит в том, чтобы идти назад, хотя список)

Если у вас есть Linq, то вы должны просто использоватьRemoveAll как предположил dlev.

Исходный вопрос не прояснил, что удаление других элементов, кроме текущего, должно поддерживаться, @CompuChip. Этот ответ не изменился с момента его уточнения. Palec
@Palec, я понимаю, отсюда мой комментарий. CompuChip
+1 за i-- (уменьшая i) Jeson Martajaya
Работает только при удалении текущего элемента. Если вы удаляете произвольный элемент, вам нужно проверить, был ли его индекс в / до или после текущего индекса, чтобы решить, следует ли--i. CompuChip
8

что этот пост старый, но я решил поделиться тем, что сработало для меня.

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

private void ProcessAndRemove(IList<Item> list)
{
    foreach (var item in list.ToList())
    {
        if (item.DeterminingFactor > 10)
        {
            list.Remove(item);
        }
    }
}
Отличная идея о .ToList ()! Простой шлепок, а также работает в тех случаях, когда вы напрямую не используете стандартный метод «Remove ... ()». user1172173
0

Как насчет

int[] tmp = new int[myIntCollection.Count ()];
myIntCollection.CopyTo(tmp);
foreach(int i in tmp)
{
    myIntCollection.Remove(42); //The error is no longer here.
}
В текущем C # это может быть переписано какforeach (int i in myIntCollection.ToArray()) { myIntCollection.Remove(42); } для любого перечисляемого, иList<T> специально поддерживает этот метод даже в .NET 2.0. Palec
3

Давайте добавим вам код:

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);
}
Я не могу поверить, что этот маленький трюк сработал. Спасибо Mana
20

Если вы оба должны перечислитьList<T> и удалить из него, то я предлагаю просто с помощьюwhile цикл вместоforeach

var index = 0;
while (index < myList.Count) {
  if (someCondition(myList[index])) {
    myList.RemoveAt(index);
  } else {
    index++;
  }
}
Это должно быть принятым ответом по моему мнению. Это позволяет вам рассмотреть остальные элементы в вашем списке, не повторяя список удаляемых элементов. Slvrfn
работает с IList Bill Hoag

Похожие вопросы