C# 查找两列表共有重复记录时Contains方法失效问题解析
问题根因
你的判断完全正确,这一问题的核心原因就是相等比较的实现逻辑不匹配:
- CsvHelper读取CSV生成的列表元素,默认是自定义的普通引用类型(class),这类类型没有重写值相等的逻辑,
Equals和GetHashCode方法都沿用System.Object的默认实现——也就是仅当两个对象是内存中同一个实例时,才会被判定为相等。 - 你之前写的
List<T>.Contains()和LINQ查询,默认调用的就是上述的默认引用相等逻辑,哪怕两条记录的所有字段值完全一致,只要是不同的内存实例,就会被判定为不匹配,因此查不到任何重复记录。 - 而你的嵌套循环手动逐字段比对值的逻辑,相当于绕过了默认的相等比较规则,直接判断字段值是否一致,所以能正常匹配到重复记录,但这种写法的时间复杂度是O(n*m),当两个列表的数据量变大时,性能会快速下降。
更简洁高效的实现方案
针对这个场景,有几种可落地的优化方式,按推荐优先级排序:
1. 从根源调整记录类型的相等逻辑
如果你的Sale类是自己定义的,优先通过类型本身实现值相等逻辑,之后你最开始写的简单Contains、LINQ代码都可以直接正常运行:
- 如果你使用的是C# 9及以上版本,直接把原来的普通class改成
record类型即可。record会自动按照所有字段的值实现相等判断和哈希计算,不需要手动写任何比较代码,比如:public record Sale( string FirstName, string LastName, string City, string Item, int Quantity, DateTime Date, decimal ItemPrice ); - 如果你必须使用普通class,就手动重写
Equals和GetHashCode方法,按照你需要匹配的所有字段实现值比较逻辑,替换掉默认的引用比较规则。
2. 自定义相等比较器,无需修改原有类定义
如果不想改动原来的Sale类,可以单独实现一个IEqualityComparer<Sale>接口,把逐字段比较的逻辑封装进去,配合LINQ的Intersect方法快速取两个列表的交集,整体时间复杂度为O(n+m),性能远高于嵌套循环:
首先编写比较器类:
public class SaleEqualityComparer : IEqualityComparer<Sale> { public bool Equals(Sale? x, Sale? y) { // 提前处理同实例、空值的边界情况 if (ReferenceEquals(x, y)) return true; if (x is null || y is null) return false; // 替换为你需要比对的所有字段逻辑 return x.FirstName == y.FirstName && x.LastName == y.LastName && x.City == y.City && x.Item == y.Item && x.Quantity == y.Quantity && x.Date == y.Date && x.ItemPrice == y.ItemPrice; } public int GetHashCode(Sale obj) { // 把所有参与相等判断的字段组合计算哈希值,保证同值对象哈希一致 return HashCode.Combine( obj.FirstName, obj.LastName, obj.City, obj.Item, obj.Quantity, obj.Date, obj.ItemPrice ); } }
之后只需要一行代码就能拿到两个列表的共同记录:
var dups = sales.Intersect(salesBackup, new SaleEqualityComparer()).ToList();
这种写法除了性能远高于嵌套循环,还会自动按照相等规则对结果去重,不会像嵌套循环那样因为双重遍历的匹配逻辑,把同一条记录多次加入结果列表。
3. 临时快速实现(无需额外写类)
如果只是临时处理数据、不想单独定义比较器类,可以借助C#匿名类型自动实现值相等的特性,通过投影匹配的方式实现同样的效果:
// 把备份列表的所有匹配字段转成哈希集合,实现常数级查找 var backupKeys = salesBackup.Select(s => new { s.FirstName, s.LastName, s.City, s.Item, s.Quantity, s.Date, s.ItemPrice }).ToHashSet(); // 匹配主列表的共同记录 var dups = sales.Where(s => backupKeys.Contains(new { s.FirstName, s.LastName, s.City, s.Item, s.Quantity, s.Date, s.ItemPrice })).ToList();
这种方法的原理是C#的匿名类型会自动按照所有属性值实现Equals和GetHashCode,和自定义比较器的效果完全一致,只是写法更临时灵活。
内容的提问来源于stack exchange,提问作者LukePerrin
相关产品推荐
相关产品推荐

