如何从一个通用列表中删除元素,而迭代呢?

我正在寻找一个更好的模式来处理每个需要处理的元素列表,然后根据结果从列表中删除。

你不能在foreach (var element in X)使用.Remove(element) foreach (var element in X) (因为它导致Collection was modified; enumeration operation may not execute. 。exception)…你也不能使用for (int i = 0; i < elements.Count(); i++).RemoveAt(i)因为它扰乱了你在集合中相对于i当前位置。

有没有一个优雅的方式来做到这一点?

用for循环反向重复列表:

 for (int i = safePendingList.Count - 1; i >= 0; i--) { // some code // safePendingList.RemoveAt(i); } 

例:

 var list = new List<int>(Enumerable.Range(1, 10)); for (int i = list.Count - 1; i >= 0; i--) { if (list[i] > 5) list.RemoveAt(i); } list.ForEach(i => Console.WriteLine(i)); 

或者,可以使用带谓词的RemoveAll方法来testing:

 safePendingList.RemoveAll(item => item.Value == someValue); 

这里有一个简单的例子来演示:

 var list = new List<int>(Enumerable.Range(1, 10)); Console.WriteLine("Before:"); list.ForEach(i => Console.WriteLine(i)); list.RemoveAll(i => i > 5); Console.WriteLine("After:"); list.ForEach(i => Console.WriteLine(i)); 

一个简单而直接的解决scheme:

使用一个标准的循环向后运行你的集合和RemoveAt(i)删除元素。

当迭代迭代时,想要从集合中移除元素时,首先应该想到反向迭代。

幸运的是,有一个更优雅的解决scheme比写一个for循环涉及不必要的打字,并可能容易出错。

 ICollection<int> test = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); foreach (int myInt in test.Reverse<int>()) { if (myInt % 2 == 0) { test.Remove(myInt); } } 
  foreach (var item in list.ToList()) { list.Remove(item); } 

如果将“.ToList()”添加到列表中(或者LINQ查询的结果),则可以直接从“list”中删除“item”,而不会导致可怕的“ Collection被修改;枚举操作可能不会执行” 。 错误。 编译器创build“list”的副本,以便您可以安全地对数组执行删除操作。

虽然这种模式不是超高效的,但是它有一种自然的感觉, 几乎适用于任何情况 。 例如,当您要将每个“项目”保存到数据库,并且只有在数据库保存成功时才从列表中删除它。

select你想要的元素,而不是试图去除你想要的元素。 这比删除元素要容易得多(通常也更高效)。

 var newSequence = (from el in list where el.Something || el.AnotherThing < 0 select el); 

我想发表这个评论以回应Michael Dillon留下的评论,但这个评论太长了,可能对我的回答有用:

就我个人而言,如果您确实需要删除项目,我绝不会逐个删除项目,然后调用RemoveAll来获取谓词,并只重新安排一次内部数组,而Remove对您删除的每个元素执行Array.Copy操作。 RemoveAll更加高效。

而当你向后遍历一个列表时,你已经有了要删除的元素的索引,所以调用RemoveAt会更有效率,因为Remove首先遍历列表来查找元素你试图删除,但你已经知道该指数。

总而言之,我没有看到有任何理由在for循环中调用Remove 。 理想情况下,如果可能的话,使用上面的代码来根据需要从列表中stream出元素,因此根本不需要创build第二个数据结构。

在通用列表中使用ToArray(),可以在通用列表上执行Remove(item):

  List<String> strings = new List<string>() { "a", "b", "c", "d" }; foreach (string s in strings.ToArray()) { if (s == "b") strings.Remove(s); } 

使用.ToList()将复制你的列表,正如在这个问题中所解释的: ToList() – 它是否创build一个新的列表?

通过使用ToList(),您可以从原始列表中删除,因为您实际上正在迭代副本。

 foreach (var item in listTracked.ToList()) { if (DetermineIfRequiresRemoval(item)) { listTracked.Remove(item) } } 

因为您可以使用任何删除条件

 list.RemoveAll(item => item.Value == someValue); 

如果确定删除哪些项目的function没有副作用并且不会改变项目(这是一个纯函数),那么简单而有效的(线性时间)解决scheme是:

 list.RemoveAll(condition); 

如果有副作用,我会使用像这样的东西:

 var toRemove = new HashSet<T>(); foreach(var item in items) { ... if(condition) toRemove.Add(item); } items.RemoveAll(toRemove.Contains); 

这仍然是线性时间,假设哈希值是好的。 但是由于哈希集的原因,它的内存使用增加了。

最后如果你的列表只是一个IList<T>而不是一个List<T>我build议我的答案我该怎么做这个特殊的foreach迭代器? 。 这与IList<T>典型实现具有线性运行时相比,许多其他答案的二次运行时相比。

 List<T> TheList = new List<T>(); TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element)); 

你不能使用foreach,但是当你删除一个项目时,你可以迭代转发和pipe理你的循环索引variables,如下所示:

 for (int i = 0; i < elements.Count; i++) { if (<condition>) { // Decrement the loop counter to iterate this index again, since later elements will get moved down during the remove operation. elements.RemoveAt(i--); } } 

请注意,通常所有这些技术都依赖于正在迭代的集合的行为。 这里显示的技术将与标准列表(T)一起使用。 (这是很有可能编写自己的集合类和迭代器, 允许在foreach循环中删除项目。)

在列表中使用RemoveRemoveAt时,故意使用迭代的方式很难,因为几乎总是做错的事情 。 你可能能够用一些聪明的技巧来实现它,但是这将会非常缓慢。 每次调用Remove ,都必须扫描整个列表才能find要删除的元素。 每次调用RemoveAt ,都必须将后续元素向左移动1个位置。 因此,使用RemoveRemoveAt任何解决scheme都需要二次时间O(n2)

如果可以,请使用RemoveAll 。 否则,以下模式将在线性时间O(n)中 就地过滤列表。

 // Create a list to be filtered IList<int> elements = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); // Filter the list int kept = 0; for (int i = 0; i < elements.Count; i++) { // Test whether this is an element that we want to keep. if (elements[i] % 3 > 0) { // Add it to the list of kept elements. elements[kept] = elements[i]; kept++; } } // Unfortunately IList has no Resize method. So instead we // remove the last element of the list until: elements.Count == kept. while (kept < elements.Count) elements.RemoveAt(elements.Count-1); 

希望 “模式”是这样的:

 foreach( thing in thingpile ) { if( /* condition#1 */ ) { foreach.markfordeleting( thing ); } elseif( /* condition#2 */ ) { foreach.markforkeeping( thing ); } } foreachcompleted { // then the programmer's choices would be: // delete everything that was marked for deleting foreach.deletenow(thingpile); // ...or... keep only things that were marked for keeping foreach.keepnow(thingpile); // ...or even... make a new list of the unmarked items others = foreach.unmarked(thingpile); } 

这将使代码与程序员大脑中正在进行的过程保持一致。

我将从LINQ查询中重新分配列表,以滤除不想保留的元素。

 list = list.Where(item => ...).ToList(); 

除非这个清单非常大,否则在这个过程中不应该有重大的性能问题。

通过假设谓词是一个元素的布尔属性,如果它是真的,那么该元素应该被删除:

  int i = 0; while (i < list.Count()) { if (list[i].predicate == true) { list.RemoveAt(i); continue; } i++; } 

我发现自己处于类似的情况,我不得不删除给定List<T>中的每个 n 元素。

 for (int i = 0, j = 0, n = 3; i < list.Count; i++) { if ((j + 1) % n == 0) //Check current iteration is at the nth interval { list.RemoveAt(i); j++; //This extra addition is necessary. Without it j will wrap //down to zero, which will throw off our index. } j++; //This will always advance the j counter } 

从列表中删除项目的成本与要删除的项目数量成正比。 在前半部分项目符合删除条件的情况下,任何基于单独删除项目的方法最终将不得不执行大约N * N / 4个项目复制操作,如果列表很大,可能会非常昂贵。

更快的方法是扫描列表以find要移除的第一个项目(如果有的话),然后从该点向前复制每个应该保留的项目到它所属的位置。 一旦完成,如果R项目应该保留,列表中的第一个R项目将是那些R项目,并且所有需要删除的项目将在最后。 如果这些项目以相反顺序被删除,则系统将不必最终复制它们中的任何一个,因此如果该列表具有保留了包括所有第一个F的R项目的N个项目,则有必要复制RF项目,并且将该列表缩小一个项目NR次。 所有线性时间。

我的做法是,我首先创build一个索引列表,它应该被删除。 之后我循环索引并从最初的列表中删除项目。 这看起来像这样:

 var messageList = ...; // Restrict your list to certain criteria var customMessageList = messageList.FindAll(m => m.UserId == someId); if (customMessageList != null && customMessageList.Count > 0) { // Create list with positions in origin list List<int> positionList = new List<int>(); foreach (var message in customMessageList) { var position = messageList.FindIndex(m => m.MessageId == message.MessageId); if (position != -1) positionList.Add(position); } // To be able to remove the items in the origin list, we do it backwards // so that the order of indices stays the same positionList = positionList.OrderByDescending(p => p).ToList(); foreach (var position in positionList) { messageList.RemoveAt(position); } } 

复制你正在迭代的列表。 然后从副本中删除并交换原件。 向后退是令人困惑的,并行循环时效果不佳。

 var ids = new List<int> { 1, 2, 3, 4 }; var iterableIds = ids.ToList(); Parallel.ForEach(iterableIds, id => { ids.Remove(id); }); 

迭代时从列表中删除项目的最好方法是使用RemoveAll() 。 但人们主要关心的是他们必须在循环内部做一些复杂的事情和/或有复杂的比较情况。

解决scheme是仍然使用RemoveAll()但使用这个表示法:

 var list = new List<int>(Enumerable.Range(1, 10)); list.RemoveAll(item => { // Do some complex operations here // Or even some operations on the items SomeFunction(item); // In the end return true if the item is to be removed. False otherwise return item > 5; }); 
 myList.RemoveAt(i--); simples;