如何在ObservableCollection<T>中搜索并移除空项?求更优实现方案
首先,你遇到的Collection was modified; enumeration operation may not execute异常是.NET里的常见问题——foreach遍历集合时,集合的结构不能被修改(比如添加/移除项),因为枚举器是基于遍历开始时的集合状态创建的,一旦集合结构变化,枚举器就失效了。
你用Linq生成新集合的方式是可行的,但如果你的需求是直接修改原ObservableCollection(比如它绑定了UI,需要实时更新),或者想避免创建新集合实例,这里有几种更优的实现方式:
1. 反向遍历移除项
反向遍历是最直接的修改原集合的方式,因为从最后一项往前遍历,移除当前项不会影响前面未遍历的项的索引:
// 从最后一个元素开始往前遍历 for (int i = ListOfTnAGroupAccs.Count - 1; i >= 0; i--) { var item = ListOfTnAGroupAccs[i]; // 用string.IsNullOrEmpty比判断==""更严谨,能同时处理null和空字符串 if (string.IsNullOrEmpty(item.Prefix) && string.IsNullOrEmpty(item.ReportingGrp) && string.IsNullOrEmpty(item.AccMngInitials) && string.IsNullOrEmpty(item.Description)) { ListOfTnAGroupAccs.RemoveAt(i); } }
这种方式不需要额外创建临时集合,性能也不错,适合大多数场景。
2. 先收集要移除的项,再批量移除
先把符合移除条件的项收集到一个独立列表中,再遍历这个列表移除原集合的项——这样不会干扰原集合的枚举:
// 先筛选出所有要移除的项,存入临时列表 var itemsToRemove = ListOfTnAGroupAccs .Where(item => string.IsNullOrEmpty(item.Prefix) && string.IsNullOrEmpty(item.ReportingGrp) && string.IsNullOrEmpty(item.AccMngInitials) && string.IsNullOrEmpty(item.Description)) .ToList(); // 遍历临时列表,移除原集合中的项 foreach (var item in itemsToRemove) { ListOfTnAGroupAccs.Remove(item); }
这种方式逻辑清晰,容易理解,而且如果需要对要移除的项做额外处理(比如日志),也很方便。
3. 替换整个ObservableCollection(如果允许)
如果你的ListOfTnAGroupAccs是可重新赋值的属性(比如实现了INotifyPropertyChanged),可以直接用Linq生成新的ObservableCollection并赋值,这样UI绑定会自动更新:
// 用Linq筛选出有效项,创建新的ObservableCollection ListOfTnAGroupAccs = new ObservableCollection<Model>( ListOfTnAGroupAccs.Where(item => !string.IsNullOrEmpty(item.Prefix) && !string.IsNullOrEmpty(item.ReportingGrp) && !string.IsNullOrEmpty(item.AccMngInitials) && !string.IsNullOrEmpty(item.Description)) );
这种方式代码最简洁,但要注意:如果有其他地方引用了原来的集合实例,替换后这些引用会指向旧实例,需要根据场景判断是否适用。
4. 给ObservableCollection添加RemoveAll扩展方法
如果你的项目中经常需要批量移除ObservableCollection的项,可以写一个扩展方法来复用逻辑:
public static class ObservableCollectionExtensions { public static void RemoveAll<T>(this ObservableCollection<T> collection, Func<T, bool> predicate) { // 先收集要移除的项,避免遍历原集合时修改它 var itemsToRemove = collection.Where(predicate).ToList(); foreach (var item in itemsToRemove) { collection.Remove(item); } } }
之后使用起来就非常简洁:
ListOfTnAGroupAccs.RemoveAll(item => string.IsNullOrEmpty(item.Prefix) && string.IsNullOrEmpty(item.ReportingGrp) && string.IsNullOrEmpty(item.AccMngInitials) && string.IsNullOrEmpty(item.Description));
额外小建议
把判断空字符串的逻辑封装成Model的一个属性,会让代码更整洁:
public class Model { // 你的原有属性 public string Prefix { get; set; } public string ReportingGrp { get; set; } public string AccMngInitials { get; set; } public string Description { get; set; } // 添加判断是否为空项的属性 public bool IsEmptyItem => string.IsNullOrEmpty(Prefix) && string.IsNullOrEmpty(ReportingGrp) && string.IsNullOrEmpty(AccMngInitials) && string.IsNullOrEmpty(Description); }
这样上面的移除逻辑可以简化为:
// 反向遍历示例 for (int i = ListOfTnAGroupAccs.Count - 1; i >= 0; i--) { if (ListOfTnAGroupAccs[i].IsEmptyItem) { ListOfTnAGroupAccs.RemoveAt(i); } }
内容的提问来源于stack exchange,提问作者AliAzra

