C# 如何有条件地从 .NET 集合中删除项目

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/653596/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 11:59:58  来源:igfitidea点击:

How to conditionally remove items from a .NET collection

c#.netcollectionsextension-methods

提问by Lee D

I'm trying to write an extension method in .NET that will operate on a generic collection, and remove all items from the collection that match a given criteria.

我正在尝试在 .NET 中编写一个扩展方法,该方法将对通用集合进行操作,并从集合中删除与给定条件匹配的所有项目。

This was my first attempt:

这是我的第一次尝试:

public static void RemoveWhere<T>(this ICollection<T> Coll, Func<T, bool> Criteria){
    foreach (T obj in Coll.Where(Criteria))
        Coll.Remove(obj);
}

However this throws an InvalidOperationException, "Collection was modified; enumeration operation may not execute". Which does make sense, so I made a second attempt with a second collection variable to hold the items that need to be removed and iterate through that instead:

但是,这会引发 InvalidOperationException,“集合已修改;枚举操作可能无法执行”。这确实有道理,所以我再次尝试使用第二个集合变量来保存需要删除的项目并迭代它:

public static void RemoveWhere<T>(this ICollection<T> Coll, Func<T, bool> Criteria){
    List<T> forRemoval = Coll.Where(Criteria).ToList();

    foreach (T obj in forRemoval)
        Coll.Remove(obj);
}

This throws the same exception; I'm not sure I really understand why as 'Coll' is no longer the collection being iterated over, so why can't it be modified?

这会引发相同的异常;我不确定我是否真的理解为什么 'Col' 不再是被迭代的集合,那么为什么不能修改它呢?

If anyone has any suggestions as to how I can get this to work, or a better way to achieve the same, that'd be great.

如果有人对我如何使其工作有任何建议,或者有更好的方法来实现这一目标,那就太好了。

Thanks.

谢谢。

采纳答案by Marc Gravell

For List<T>, this exists already, as RemoveAll(Predicate<T>). As such, I'd suggest that you keep the name (allowing familiarity, and precedence).

对于List<T>,这已经存在,作为RemoveAll(Predicate<T>)。因此,我建议您保留该名称(允许熟悉和优先)。

Basically, you can't remove while iterating. There are two common options:

基本上,您不能在迭代时删除。有两种常见的选择:

  • use indexer based iteration (for) and removal
  • buffer the items to remove, and remove after the foreach(as you've already done)
  • 使用基于索引器的迭代 ( for) 和删除
  • 缓冲要删除的项目,然后删除foreach(正如您已经完成的那样)

So perhaps:

所以也许:

public static void RemoveAll<T>(this IList<T> list, Func<T, bool> predicate) {
    for (int i = 0; i < list.Count; i++) {
        if (predicate(list[i])) {
            list.RemoveAt(i--);
        }
    }
}

Or more generally for any ICollection<T>:

或更一般地,对于 any ICollection<T>

public static void RemoveAll<T>(this ICollection<T> collection, Func<T, bool> predicate) {
    T element;

    for (int i = 0; i < collection.Count; i++) {
        element = collection.ElementAt(i);
        if (predicate(element)) {
            collection.Remove(element);
            i--;
        }
    }
}

This approach has the advantage of avoiding lots of extra copies of the list.

这种方法的优点是避免了列表的大量额外副本。

回答by Simon Steele

I just tested it, and your second method works fine (as it should). Something else must be going wrong, can you provide a bit of sample code that shows the problem?

我刚刚对其进行了测试,您的第二种方法效果很好(应该如此)。肯定还有其他问题,您能否提供一些显示问题的示例代码?

List<int> ints = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

ints.RemoveWhere(i => i > 5);
foreach (int i in ints)
{
    Console.WriteLine(i);
}

Gets:

获取:

1
2
3
4
5

回答by Jon Skeet

As Marc said, List<T>.RemoveAll()is the way to go for lists.

正如马克所说,List<T>.RemoveAll()是列表的方式。

I'm surprised your second version didn't work though, given that you've got the call to ToList()after the Where()call. Without the ToList()call it would certainly make sense (because it would be evaluated lazily), but it should be okay as it is. Could you show a short but complete example of this failing?

我很惊讶你的第二个版本不起作用,因为你在通话ToList()Where()接到了电话。没有ToList()调用它肯定是有意义的(因为它会被懒惰地评估),但它应该没问题。你能举一个简短但完整的例子来说明这个失败吗?

EDIT: Regarding your comment in the question, I still can't get it to fail. Here's a short but completeexample which works:

编辑:关于您在问题中的评论,我仍然无法让它失败。这是一个简短但完整的示例,它有效:

using System;
using System.Collections.Generic;
using System.Linq;

public class Staff
{
    public int StaffId;
}

public static class Extensions
{
    public static void RemoveWhere<T>(this ICollection<T> Coll,
                                      Func<T, bool> Criteria)
    {
        List<T> forRemoval = Coll.Where(Criteria).ToList();

        foreach (T obj in forRemoval)
        {
            Coll.Remove(obj);
        }
    }
}

class Test
{
    static void Main(string[] args)
    {
        List<Staff> mockStaff = new List<Staff>
        {
            new Staff { StaffId = 3 },
            new Staff { StaffId = 7 }
        };

       Staff newStaff = new Staff{StaffId = 5};
       mockStaff.Add(newStaff);
       mockStaff.RemoveWhere(s => s.StaffId == 5);

       Console.WriteLine(mockStaff.Count);
    }
}

If you could provide a similar completeexample which fails, I'm sure we can work out the reason.

如果你能提供一个类似的完整例子,但我相信我们可以找出原因。

回答by Dustin Campbell

I just tried your second example and it seems to work fine:

我刚刚试过你的第二个例子,它似乎工作正常:

Collection<int> col = new Collection<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
col.RemoveWhere(x => x % 2 != 0);

foreach (var x in col)
    Console.WriteLine(x);
Console.ReadLine();

I didn't get an exception.

我没有得到例外。

回答by Bengt

Another version of Marcs RemoveAll:

另一个版本的Marcs RemoveAll:

public static void RemoveAll<T>(this IList<T> list, Func<T, bool> predicate)
{
    int count = list.Count;
    for (int i = count-1; i > -1; i--)
    {
        if (predicate(list[i]))
        {
            list.RemoveAt(i);
        }
    }
}