关于c#:如何有条件地从.NET集合中删除项目

How to conditionally remove items from a .NET collection

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

这是我第一次尝试:

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

但是,这会引发InvalidOperationException,"集合已修改;枚举操作可能不会执行"。这是有意义的,所以我用第二个集合变量进行了第二次尝试,以保存需要删除的项,并迭代该项:

1
2
3
4
5
6
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"不再是正在迭代的集合,那么为什么不能修改它呢?

如果有人对我如何才能让它工作有任何建议,或者有更好的方法来实现它,那就太好了。

谢谢。


对于List来说,这已经存在了,就像RemoveAll(Predicate)一样。因此,我建议您保留名称(允许熟悉和优先)。

基本上,迭代时不能移除。共有两种选择:

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

所以也许:

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
8
9
10
11
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--;
        }
    }
}

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


正如马克所说,List.RemoveAll()是一个列表。

不过,我很惊讶你的第二个版本不能正常工作,因为你在Where()呼叫后接到了ToList()的呼叫。如果没有ToList()的调用,它肯定是有意义的(因为它的评估会很懒惰),但它应该是正常的。你能举一个简短但完整的失败例子吗?

编辑:关于你在这个问题上的评论,我仍然不能让它失败。下面是一个简短但完整的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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);
    }
}

如果你能提供一个同样完整的失败例子,我相信我们能找出原因。


我刚试过你的第二个例子,它看起来很好用:

1
2
3
4
5
6
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();

我没有例外。


我刚测试过它,你的第二种方法也很好用(应该是这样)。一定是出了什么问题,您能提供一些显示问题的示例代码吗?

1
2
3
4
5
6
7
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
1
2
3
4
5


另一个版本的marcs removeall:

1
2
3
4
5
6
7
8
9
10
11
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);
        }
    }
}