C# 使用 LINQ 进行集合运算与引用类型去重策略
基础值类型的集合运算
C# 标准库提供了便捷的 LINQ 扩展方法处理常见集合操作。对于整型等值类型,可直接调用对应方法获取结果:
var sourceValues = new[] { 10, 20, 30, 40, 50 };
var targetValues = new[] { 20, 40, 50, 60, 70, 80 };
// 提取两集公共元素
var intersection = sourceValues.Intersect(targetValues).ToList();
// 源集存在而目标集不存在的元素
var diffFromSource = sourceValues.Except(targetValues).ToList();
// 目标集存在而源集不存在的元素
var diffFromTarget = targetValues.Except(sourceValues).ToList();
// 合并集合并自动剔除重复值
var combinedSet = sourceValues.Union(targetValues).ToList();
引用类型对象的集合处理
当集合元素为自定义类时,直接调用上述方法会默认基于引用地址进行比较,通常不符合业务需求。此时需传入实现了 IEqualityComparer<T> 接口的实例:
public class ProductItem
{
public string ItemName { get; set; }
public int StockId { get; set; }
}
var warehouseA = new[]
{
new ProductItem { ItemName = "Laptop", StockId = 501 },
new ProductItem { ItemName = "Monitor", StockId = 502 }
};
var warehouseB = new[]
{
new ProductItem { ItemName = "Laptop", StockId = 501 },
new ProductItem { ItemName = "Speaker", StockId = 503 }
};
// 执行集合操作时需传递比较器实例
var mergedInventory = warehouseA.Union(warehouseB, new ProductComparer()).ToList();
var sharedStock = warehouseA.Intersect(warehouseB, new ProductComparer()).ToList();
实现自定义比较器
重写 IEqualityComparer<T> 必须同步实现 Equals 与 GetHashCode,以维持哈希表容器的查询效率与逻辑一致性。
单字段匹配
若仅依赖单一属性(如数据库主键)判定对象等同:
public class ProductComparer : IEqualityComparer<ProductItem>
{
public bool Equals(ProductItem x, ProductItem y)
{
if (ReferenceEquals(x, y)) return true;
if (x is null || y is null) return false;
return x.StockId == y.StockId;
}
public int GetHashCode(ProductItem obj)
{
if (obj is null) return 0;
return obj.StockId.GetHashCode();
}
}
复合条件匹配
当多个属性共同构成业务唯一键时,需将各字段哈希值进行组合:
public class CompositeProductComparer : IEqualityComparer<ProductItem>
{
public bool Equals(ProductItem x, ProductItem y)
{
if (ReferenceEquals(x, y)) return true;
if (x is null || y is null) return false;
return x.ItemName == y.ItemName && x.StockId == y.StockId;
}
public int GetHashCode(ProductItem obj)
{
if (obj is null) return 0;
int nameHash = obj.ItemName?.GetHashCode() ?? 0;
int idHash = obj.StockId.GetHashCode();
// 异或运算组合哈希值
return nameHash ^ idHash;
}
}
泛型工具类简化开发
高频手写比较器类易产生冗余代码。针对单字段场景,可封装通用比较器并配合扩展方法提升语法整洁度:
public class PropertyComparer<T, TProp> : IEqualityComparer<T>
{
private readonly Func<T, TProp> _keySelector;
public PropertyComparer(Func<T, TProp> selector) => _keySelector = selector;
public bool Equals(T x, T y) => EqualityComparer<TProp>.Default.Equals(_keySelector(x), _keySelector(y));
public int GetHashCode(T obj) => _keySelector(obj) is null ? 0 : _keySelector(obj).GetHashCode();
}
public static class EnumerableExtensions
{
/// <summary>
/// 依据指定属性投影执行去重
/// </summary>
public static IEnumerable<T> UniqueBy<T, TProp>(this IEnumerable<T> source, Func<T, TProp> keySelector)
{
return source.Distinct(new PropertyComparer<T, TProp>(keySelector));
}
}
调用方式显著简化:
var distinctWarehouseA = warehouseA.UniqueBy(p => p.ItemName).ToList();
多字段泛型方案说明
若需支持类似 s => s.Id && s.Name 的多条件去重,可将目标属性封装为匿名对象或元组作为委托返回值,再次复用上述泛型比较器即可。LINQ 交集、并集、差集与去重操作的底层核心机制统一依赖相等比较器接口。实际工程中应结合 Microsoft 官方 API 文档查阅边界行为与时间复杂度特性,部分关联查询场景可替代性使用 Join 实现数据对齐。