在C#中枚举的同时从List <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 ,因为枚举器不会在底层集合更改时不喜欢。

我需要在迭代过程中对集合进行更改。 有许多模式可以用来避免这种情况 ,但似乎没有一个好的解决scheme:

  1. 不要在这个循环中删除,而应该在主循环之后保留一个单独的“删除列表”。

    这通常是一个很好的解决scheme,但在我的情况下,我需要项目立即消失,因为“等待”,直到主循环之后真正删除项目会改变我的代码的逻辑stream程。

  2. 而不是删除项目,只需在项目上设置一个标志,并将其标记为不活动。 然后添加模式1的function来清理列表。

    这可以满足我所有的需求,但是这意味着很多代码将不得不改变,以便每次访问一个项目时检查非激活标志。 这是太多的pipe理,我喜欢。

  3. 以某种方式将模式2的思想融入从List<T>派生的类中。 这个超级列表将处理非活动标志,在事实之后删除对象,并且也不会将枚举消费者标记为不活动的项目。 基本上,它只是封装了模式2的所有想法(以及随后的模式1)。

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

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

    这对我来说似乎是一个不好的devise模式,或者它可以吗?

细节:

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

  • 在循环内部,我将做各种过程,添加,删除等,所以解决scheme需要相当通用。

  • 我需要删除的项目可能不是循环中的当前项目。 例如,我可能在30个项目循环的项目10上,需要删除项目6或项目26.向后走过arrays将不再工作,因为这一点。 O(

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

 myList.RemoveAll(x => x.SomeProp == "SomeValue"); 

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

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

这假定你的循环当然是专门用于清除目的的。 如果你需要额外的处理,那么最好的方法通常是使用forwhile循环,因为那时你不使用枚举器:

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

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

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

当你需要迭代一个列表,并可能在循环中修改它,那么你最好使用for循环:

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

当然,你一定要小心,例如,当我删除一个项目时,我递减,否则我们将跳过项目(另一种方法是通过列表后退)。

如果你有Linq,那么你应该像dlev所build议的那样使用RemoveAll

我知道这个post是旧的,但我想我会分享什么为我工作。

创build枚举的列表副本,然后在每个循环中,您可以处理复制的值,并删除/添加任何与源列表。

 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,但概念是一样的

让我们添加你的代码:

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

列举列表时,将要保留的列表添加到新列表中。 之后,将新列表分配给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; 

怎么样

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

如果您对高性能感兴趣,可以使用两个列表。 下面最大限度地减less了垃圾收集,最大限度地增加了内存的位置,并且从不从一个列表中删除一个项目,如果它不是最后一个项目,效率非常低。

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