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

时间:2009-03-17 09:58:11

标签: c# .net 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);
}

但是,这会引发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);
}

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

如果有人对如何使其发挥作用有任何建议,或者更好的方法来实现同样的目标,那就太好了。

感谢。

5 个答案:

答案 0 :(得分: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--);
        }
    }
}

或更常见的是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--;
        }
    }
}

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

答案 1 :(得分: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);
    }
}

如果您提供类似的完整示例失败,我相信我们可以解决原因。

答案 2 :(得分:1)

我刚试过它,你的第二种方法运行正常(应该如此)。还有其他一些问题,你能提供一些显示问题的示例代码吗?

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);
}

获取:

1
2
3
4
5

答案 3 :(得分:1)

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

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();

我没有例外。

答案 4 :(得分:0)

另一个版本的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);
        }
    }
}