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

Lee*_*e D 16 .net c# collections extension-methods

我正在尝试在.NET中编写一个将在泛型集合上运行的扩展方法,并从集合中删除与给定条件匹配的所有项目.

这是我的第一次尝试:

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

但是,这会抛出InvalidOperationException,"Collection已被修改;枚举操作可能无法执行".这是有意义的,所以我第二次尝试使用第二个集合变量来保存需要删除的项目并反复遍历:

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);
}
Run Code Online (Sandbox Code Playgroud)

这引发了同样的异常; 我不确定我真的理解为什么'Coll'不再是迭代的集合,为什么不能修改它?

如果有人对如何使这个工作有任何建议,或者更好的方法来实现同样的目标,那就太好了.

谢谢.

Mar*_*ell 38

因为List<T>,这已经存在了RemoveAll(Predicate<T>).因此,我建议你保留名称(允许熟悉和优先).

基本上,迭代时无法删除.有两种常见的选择:

  • 使用基于索引器的迭代(for)和删除
  • 缓冲要删除的项目,并在foreach(如您已经完成)之后删除

所以也许:

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--);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

或更普遍的任何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--;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这种方法的优点是可以避免大量额外的列表副本.

  • 当我使用你的第一个常用选项从集合中删除时,我通常将Count存储在一个int n变量中,让循环从头到尾重新计数,不需要i--.在循环内保存Count()调用.(过早优化,我知道......) (2认同)

Jon*_*eet 7

正如Marc所说,List<T>.RemoveAll()是列表的方法.

我很惊讶你的第二个版本没有用,因为你已经接到电话ToList()后的Where()电话.如果没有ToList()电话,它肯定会有意义(因为它会被懒惰地评估),但它应该没问题.你能否展示一下这个失败的简短但完整的例子?

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您能提供一个类似的完整示例失败,我相信我们可以找出原因.