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"不再是正在迭代的集合,那么为什么不能修改它呢?
如果有人对我如何才能让它工作有任何建议,或者有更好的方法来实现它,那就太好了。
谢谢。
对于
基本上,迭代时不能移除。共有两种选择:
- 使用基于索引器的迭代(
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--); } } } |
或者更一般地说,对于任何
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--; } } } |
号
这种方法的优点是避免了大量额外的列表副本。
正如马克所说,
不过,我很惊讶你的第二个版本不能正常工作,因为你在
编辑:关于你在这个问题上的评论,我仍然不能让它失败。下面是一个简短但完整的示例:
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); } } } |
。