Интеллектуальный способ удаления элементов из списка <T> при перечислении в С#

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

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>. Этот Superlist будет обрабатывать неактивный флаг, удаление объектов после факта, а также не будет выставлять элементы, помеченные как неактивные для пользователей перечисления. В принципе, он просто инкапсулирует все идеи шаблона 2 (а затем шаблон 1).

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

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

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

Подробнее:

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

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

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

Ответ 1

Лучшим решением обычно является метод 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));

Ответ 2

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

var index = 0;
while (index < myList.Count) {
  if (someCondition(myList[index])) {
    myList.RemoveAt(index);
  } else {
    index++;
  }
}

Ответ 3

Когда вам нужно перебирать список и изменять его во время цикла, вам лучше использовать цикл for:

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

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

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

Ответ 4

Я знаю, что этот пост старый, но я думал, что поделюсь тем, что сработало для меня.

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

Private Sub RemoveMyObjectsFromMyList(MyList As List(Of MyObject))

    For Each obj As MyObject In MyList.ToList
        If obj.DeterminingValue > 10 Then
            MyList.Remove(obj)
        End If
    Next

End Sub

Мой ответ в vb.net, но концепция такая же

Ответ 5

Добавьте код:

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

Ответ 6

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

Ответ 7

Как насчет

int[] tmp = new int[myIntCollection.Count ()];
myIntCollection.CopyTo(tmp);
foreach(int i in tmp)
{
    myIntCollection.Remove(42); //The error is no longer here.
}

Ответ 8

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

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