Distinct未去重是因为默认按引用比较;自定义类需实现IEquatable或传IEqualityComparer;GroupBy+Select可轻量单字段去重;IQueryable应避免提前ToList以防内存溢出。

Distinct 方法为什么没去重?
因为 Distinct() 默认只对值类型或实现了 IEquatable<t></t> 的引用类型做浅层比较;如果 List 里是自定义类(比如 User),它会按引用地址判断是否重复,而不是按字段内容。
- 常见错误现象:
list.Distinct().ToList()返回原样,明明两个对象字段完全一样却没被去掉 - 使用场景:处理从数据库查出的实体列表、解析 JSON 后的对象集合等
- 解决方法:传入自定义
IEqualityComparer<t></t>,或让类实现IEquatable<t></t> - 性能影响:每次比较都调用
Equals()和GetHashCode(),字段越多、逻辑越复杂,开销越大
用 Lambda 写 IEqualityComparer 很快但要注意什么?
可以借助 System.Linq 外部的扩展类(如 EqualityComparer.Create())或手写匿名比较器,但 C# 10+ 才原生支持 EqualityComparer.Create<t>(Func<t>, Func<t>)</t></t></t>。
- 兼容性注意:.NET 5/6 可直接用
EqualityComparer.Create<user>((a,b) => a.Id == b.Id, u => u.Id.GetHashCode())</user> - 旧版本(如 .NET Framework 4.7.2)需自己实现
IEqualityComparer<user></user>类,不能只靠 Lambda - 容易踩的坑:
GetHashCode()必须和Equals()逻辑一致,否则Distinct()行为不可预测 - 示例:
users.Distinct(EqualityComparer.Create<user>((x,y) => x.Name == y.Name && x.Age == y.Age, u => u.Name.GetHashCode() ^ u.Age.GetHashCode())).ToList()</user>
想按单个字段去重,有没有更轻量的办法?
不需要写完整比较器,用 GroupBy() + Select() 更直观,尤其适合临时脚本或调试场景。
- 适用情况:只要保留每个分组第一个元素,比如“按
ProductId去重,取最早一条” - 写法:
list.GroupBy(x => x.ProductId).Select(g => g.First()).ToList() - 性能差异:比
Distinct()多一次遍历,但代码意图清晰,调试时容易加断点观察分组结果 - 注意:如果字段可能为
null(如string类型 Id),GroupBy()仍能正常工作,而某些自定义比较器没处理null会抛NullReferenceException
Distinct 和 ToList 的顺序会影响结果吗?
不影响去重逻辑,但影响执行时机和内存行为。
-
list.AsEnumerable().Distinct().ToList()是 OK 的,但list.Distinct().ToList()和list.ToList().Distinct()效果一样——后者多一次无意义复制 - 真正要小心的是延迟执行:如果
list是 EF Core 的IQueryable,直接调Distinct()会被翻译成 SQL 的DISTINCT;一旦中间用了ToList(),就变成内存去重,数据量大时可能 OOM - 所以关键不是顺序本身,而是源头类型:查库用
IQueryable就别提前ToList(),本地 List 就不用纠结
实际用的时候,最容易被忽略的是 GetHashCode() 和 Equals() 的一致性,哪怕只改了一个字段的比较逻辑,也得同步更新哈希计算,不然 Distinct() 在某些数据分布下会漏掉重复项。


















