在C#中枚举时从List <T>中删除项目的智能方法

Joh*_*ock 83 c# foreach enumeration list

我有一个经典的例子,试图从一个集合中删除一个项目,同时在循环中枚举它:

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.
}
Run Code Online (Sandbox Code Playgroud)

在发生更改后的迭代开始时,InvalidOperationException会抛出一个,因为枚举器在底层集合发生更改时不喜欢.

我需要在迭代时对集合进行更改.有许多模式可用于避免这种情况,但它们似乎都没有一个好的解决方案:

  1. 不要在此循环内删除,而是保留一个单独的"删除列表",在主循环后处理.

    这通常是一个很好的解决方案,但在我的情况下,我需要项目立即消失为"等待"直到主循环后真正删除项目更改我的代码的逻辑流程.

  2. 只需在项目上设置一个标志并将其标记为非活动状态,而不是删除该项目.然后添加模式1的功能以清理列表.

    工作为我所有的需求,但它意味着一个很大的代码将在开始按每一个项目被访问时检查不活动的标志改变.根据我的喜好,这是太多的管理.

  3. 以某种方式将模式2的思想融入到源自的类中List<T>.此超级列表将处理非活动标志,事后删除对象,也不会将标记为非活动的项目暴露给枚举使用者.基本上,它只是封装了模式2(以及随后的模式1)的所有想法.

    这样的课程存在吗?有没有人有这个代码?或者,还有更好的方法?

  4. 我被告知访问myIntCollection.ToArray()而不是myIntCollection将解决问题,并允许我在循环内删除.

    这对我来说似乎是一个糟糕的设计模式,或者它可能没问题?

细节:

  • 该列表将包含许多项目,我将只删除其中的一些项目.

  • 在循环内部,我将执行各种过程,添加,删除等,因此解决方案需要相当通用.

  • 我需要删除的项目可能不是循环中的当前项目.例如,我可能在30项循环的项目10上并且需要移除项目6或项目26.因此,向后穿过阵列将不再起作用.O(

dle*_*lev 192

最好的解决方案通常是使用该RemoveAll()方法:

myList.RemoveAll(x => x.SomeProp == "SomeValue");
Run Code Online (Sandbox Code Playgroud)

或者,如果您需要删除某些元素:

MyListType[] elems = new[] { elem1, elem2 };
myList.RemoveAll(x => elems.Contains(x));
Run Code Online (Sandbox Code Playgroud)

当然,这假设您的循环仅用于删除目的.如果你确实需要额外的处理,那么最好的方法通常是使用foror while循环,因为那时你没有使用枚举器:

for (int i = myList.Count - 1; i >= 0; i--)
{
    // Do processing here, then...
    if (shouldRemoveCondition)
    {
        myList.RemoveAt(i);
    }
}
Run Code Online (Sandbox Code Playgroud)

向后移动可确保您不会跳过任何元素.

对编辑的回应:

如果您将删除看似任意的元素,最简单的方法可能是只跟踪要删除的元素,然后立即删除它们.像这样的东西:

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));
Run Code Online (Sandbox Code Playgroud)


Jar*_*Par 21

如果你必须枚举List<T>并删除它,那么我建议只使用一个while循环而不是一个foreach

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


D-J*_*nes 11

我知道这篇文章很老,但我想我会分享对我有用的东西.

创建列表的副本以进行枚举,然后在for each循环中,您可以处理复制的值,并使用源列表删除/添加/任何内容.

private void ProcessAndRemove(IList<Item> list)
{
    foreach (var item in list.ToList())
    {
        if (item.DeterminingFactor > 10)
        {
            list.Remove(item);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 虽然效率很低。 (2认同)

Jus*_*tin 8

当您需要遍历列表并可能在循环期间修改它时,您最好使用for循环:

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

当然你必须要小心,例如,i每当项目被删除时我会减少,否则我们将跳过条目(另一种方法是通过列表向后).

如果您有Linq,那么您应该RemoveAll像dlev建议的那样使用.


Jam*_*ran 5

枚举列表时,将要保留的列表添加到新列表中。之后,将新列表分配给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;
Run Code Online (Sandbox Code Playgroud)