使用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 方法与断言进行测试:
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));
foreach (var item in list.ToList()) {
list.Remove(item);
}
如果你在列表(或LINQ查询结果)后面添加".ToList()",那么你可以直接从"list"中删除"item",而不会出现可怕的"Collection was modified; enumeration operation may not execute."错误。编译器会复制"list",使你能够安全地在数组上进行删除。
虽然这种模式并不是超级高效的,但它有一种自然的感觉,并且足够灵活以应对几乎任何情况,比如当你想将每个"item"保存到数据库并且只有在数据库保存成功后才从列表中删除它时。
一个简单直接的解决方案:
在您的集合上使用标准的for循环,并倒序运行它,使用RemoveAt(i)
删除元素。
当你想在遍历集合时移除元素时,应该首先考虑反向迭代。
幸运的是,有一种更加优雅的解决方案,可以避免编写需要大量打字且容易出错的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);
}
}
Reverse<T>()
创建了一个迭代器,用于反向遍历列表,但是它需要分配额外的缓冲区,大小与列表本身相同(http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,792)。`Reverse<T>不仅仅是反向遍历原始列表(不需要分配额外的内存)。因此,
ToList()和
Reverse()两者的内存消耗相同(都创建副本),但是
ToList()不对数据做任何修改。对于
Reverse<int>()`,我会想知道为什么要反转列表,出于什么目的。 - jahavReverse<T>()
创建一个新缓冲区,这确实令人失望,我不太明白为什么需要这样做。在我看来,根据 Enumerable
的底层结构,至少在某些情况下应该能够实现反向迭代而不分配线性内存。 - jedesah在泛型列表上使用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);
}
选择你想要的元素,而不是尝试删除你不想要的元素。这样做会更容易(通常也更有效率),比起删除元素。
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
。如果可能的话,最好使用上面的代码根据需要从列表中流出元素,这样就完全不需要创建第二个数据结构。
使用.ToList()将会复制你的列表,正如这个问题所解释的那样:
ToList()-- Does it Create a New List?通过使用ToList(),你可以从原始列表中删除元素,因为你实际上是在迭代一个副本。
foreach (var item in listTracked.ToList()) {
if (DetermineIfRequiresRemoval(item)) {
listTracked.Remove(item)
}
}
如果决定要删除哪些项的函数没有副作用并且不改变这些项(即为纯函数),则一个简单而高效(线性时间)的解决方案是:
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>
,我建议参考我的回答 How can I do this special foreach iterator? 。这将具有线性运行时,与许多其他答案的二次运行时相比。
只要符合条件,就可以进行任何删除操作。
list.RemoveAll(item => item.Value == someValue);
List<T> TheList = new List<T>();
TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element));
list.RemoveAll(Function(item) item.Value = somevalue)
。 - pseudocoderRemoveAll()
的时间比反向for
循环慢三倍。所以我肯定会坚持使用循环,至少在关键部分是如此。 - Crusha K. Roolforeach
迭代同一集合进行.Remove()
操作时,就会出现此错误。而使用for
循环并以相反的顺序使用RemoveAt(...)
允许我们在保持索引跟踪以避免越界的同时删除一个元素。而在使用RemoveAll()
时,不会在循环中使用,因此不存在修改集合本身的问题,因为我们没有对其进行迭代。 - Ahmad Mageed