如何正确扩展ObservableCollection<T>实现通用搜索功能?
问题解答
1. 移除泛型约束改用switch-case的可行性
可以这么做,但不推荐。移除where T: MyClassA后,你需要通过类型判断来实现不同类的搜索逻辑,示例代码如下:
public static ObservableCollection<T> Search<T>(this ObservableCollection<T> collection, string searchString) { if (collection == null || string.IsNullOrWhiteSpace(searchString)) return null; IEnumerable<T> searchResult = typeof(T) switch { Type t when t == typeof(MyClassA) => collection.Cast<MyClassA>().Where(x => x.Name.Contains(searchString) || x.Description.Contains(searchString)).Cast<T>(), Type t when t == typeof(MyClassB) => collection.Cast<MyClassB>().Where(x => x.Code.Contains(searchString) || x.Category.Contains(searchString)).Cast<T>(), Type t when t == typeof(MyClassC) => collection.Cast<MyClassC>().Where(x => x.Title.Contains(searchString) || x.Tag.Contains(searchString)).Cast<T>(), _ => Enumerable.Empty<T>() }; return new ObservableCollection<T>(searchResult); }
但这种方式存在明显弊端:
- 新增类型必须修改该方法,违反开闭原则
- 大量类型转换和判断会让代码臃肿、维护成本高
- 编译时无法校验类型是否支持搜索逻辑,只能依赖运行时判断
2. 更优方案:基于接口的抽象实现
推荐定义一个统一的搜索接口,让需要支持搜索的类自行实现,再通过泛型约束绑定接口,既保留类型安全,又支持多类型适配:
步骤1:定义搜索接口
public interface ISearchable { bool MatchesSearch(string searchString); }
步骤2:让实体类实现接口
每个类的搜索逻辑由自身负责,职责更清晰:
public class MyClassA : ISearchable { public string Name { get; set; } public string Description { get; set; } public bool MatchesSearch(string searchString) { return Name.Contains(searchString) || Description.Contains(searchString); } } public class MyClassB : ISearchable { public string Code { get; set; } public string Category { get; set; } public bool MatchesSearch(string searchString) { return Code.Contains(searchString) || Category.Contains(searchString); } }
步骤3:重构Search方法
public static ObservableCollection<T> Search<T>(this ObservableCollection<T> collection, string searchString) where T : ISearchable { if (collection == null || string.IsNullOrWhiteSpace(searchString)) return new ObservableCollection<T>(); // 返回空集合替代null,避免空引用异常 var searchResult = collection.Where(x => x.MatchesSearch(searchString)); return new ObservableCollection<T>(searchResult); }
该方案优势:
- 符合开闭原则,新增类型只需实现接口,无需修改Search方法
- 编译时类型安全,避免运行时转换错误
- 搜索逻辑与实体类绑定,代码结构更清晰
如果需要更高灵活性,也可以采用策略模式:定义ISearchStrategy<T>接口,为每个类型实现对应策略,在Search方法中传入策略即可,但代码量会稍多。
3. 复制匹配元素到新集合的优化
原方法中的foreach循环可以直接用ObservableCollection<T>的构造函数替代——它支持传入IEnumerable<T>参数,内部会批量处理元素添加,比手动循环Add更高效优雅:
// 替换原foreach逻辑 return new ObservableCollection<T>(searchResult);
另外建议将空输入时返回null的逻辑改为返回空集合,减少调用方的空引用处理成本。
内容的提问来源于stack exchange,提问作者Will
相关产品推荐
相关产品推荐

