我正在寻找一种更好的模式,用于处理每个元素的列表,然后根据结果从列表中删除。
你不能在foreach (var element in X)中使用.Remove(element)(因为它会导致Collection被修改;枚举操作可能无法执行。例外)……你也不能使用for (int I = 0;i < elements.Count();i++)和. removeat (i),因为它破坏了你在集合中相对于i的当前位置。
有没有一种优雅的方式来做到这一点?
我正在寻找一种更好的模式,用于处理每个元素的列表,然后根据结果从列表中删除。
你不能在foreach (var element in X)中使用.Remove(element)(因为它会导致Collection被修改;枚举操作可能无法执行。例外)……你也不能使用for (int I = 0;i < elements.Count();i++)和. removeat (i),因为它破坏了你在集合中相对于i的当前位置。
有没有一种优雅的方式来做到这一点?
当前回答
foreach (var item in list.ToList()) {
list.Remove(item);
}
如果你将".ToList()"添加到列表(或LINQ查询的结果)中,你可以直接从"list"中删除"item",而不用担心"Collection was modified;枚举操作不能执行"错误。编译器会复制“list”,这样你就可以安全地删除数组了。
虽然这种模式不是超级高效,但它有一种自然的感觉,并且对于几乎任何情况都足够灵活。例如,当您想要将每个“项”保存到一个DB,并仅当DB保存成功时才将其从列表中删除。
其他回答
复制您正在迭代的列表。然后从副本中删除并与原件相互作用。倒退是令人困惑的,并且在并行循环时不能很好地工作。
var ids = new List<int> { 1, 2, 3, 4 };
var iterableIds = ids.ToList();
Parallel.ForEach(iterableIds, id =>
{
ids.Remove(id);
});
List<T> TheList = new List<T>();
TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element));
使用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));
For循环是一个不好的构造。
使用时
var numbers = new List<int>(Enumerable.Range(1, 3));
while (numbers.Count > 0)
{
numbers.RemoveAt(0);
}
但是,如果你一定要用for
var numbers = new List<int>(Enumerable.Range(1, 3));
for (; numbers.Count > 0;)
{
numbers.RemoveAt(0);
}
或者,这个:
public static class Extensions
{
public static IList<T> Remove<T>(
this IList<T> numbers,
Func<T, bool> predicate)
{
numbers.ForEachBackwards(predicate, (n, index) => numbers.RemoveAt(index));
return numbers;
}
public static void ForEachBackwards<T>(
this IList<T> numbers,
Func<T, bool> predicate,
Action<T, int> action)
{
for (var i = numbers.Count - 1; i >= 0; i--)
{
if (predicate(numbers[i]))
{
action(numbers[i], i);
}
}
}
}
用法:
var numbers = new List<int>(Enumerable.Range(1, 10)).Remove((n) => n > 5);
然而,LINQ已经有RemoveAll()来做这件事
var numbers = new List<int>(Enumerable.Range(1, 10));
numbers.RemoveAll((n) => n > 5);
最后,你最好使用LINQ的Where()来过滤和创建一个新列表,而不是改变现有的列表。不变性通常是好的。
var numbers = new List<int>(Enumerable.Range(1, 10))
.Where((n) => n <= 5)
.ToList();
选择您确实需要的元素,而不是试图删除您不想要的元素。这比删除元素要容易得多(通常也更有效)。
var newSequence = (from el in list
where el.Something || el.AnotherThing < 0
select el);
我想把这篇评论作为对Michael Dillon下面评论的回应,但它太长了,可能在我的回答中有用:
就我个人而言,我从来不会一个接一个地删除项,如果你确实需要删除,那么调用RemoveAll,它接受一个谓词,只重新排列一次内部数组,而remove则是一个数组。为您删除的每个元素复制操作。RemoveAll大大提高了效率。
当你在一个列表上向后迭代时,你已经有了你想要删除的元素的索引,所以调用RemoveAt会更有效,因为remove首先遍历列表来找到你想要删除的元素的索引,但你已经知道那个索引。
总而言之,我不认为有任何理由在for循环中调用Remove。理想情况下,如果可能的话,使用上面的代码根据需要从列表中输入元素,这样就根本不需要创建第二个数据结构。