Distinct默认对引用类型仅比较内存地址,导致相同字段的不同实例无法去重;解决方法包括使用DistinctBy(.NET 6+)、实现IEquatable、自定义IEqualityComparer或GroupBy+Select,字符串需注意大小写和空格处理。

Distinct 默认行为只对引用类型做引用比较
直接对 IEnumerable<myclass></myclass> 调用 .Distinct(),结果往往“没去重”——因为默认比较器只比内存地址。哪怕两个对象所有字段都一样,只要不是同一个实例,就会被当成不同项。
解决方法是传入自定义比较器,或让类型实现 IEquatable<t></t>:
- 简单场景:用
DistinctBy(.NET 6+)最省事,比如按 Id 去重:list.DistinctBy(x => x.Id) - 老版本(.NET 5 及以下):必须写
IEqualityComparer<t></t>实现,或用GroupBy+Select(g => g.First())曲线救国 - 如果类型自己可控,直接在类里实现
IEquatable<t></t>并重写GetHashCode和Equals,后续所有 LINQ 操作(包括Distinct)都能自动生效
字符串去重要注意大小写和空格
Distinct() 对 string 默认使用 StringComparer.Ordinal,区分大小写、不忽略首尾空格。常见错误是以为 "ABC" 和 "abc" 会被合并。
实操建议:
- 忽略大小写:传
StringComparer.OrdinalIgnoreCase,如strings.Distinct(StringComparer.OrdinalIgnoreCase) - 同时去掉空格再比较:不能直接塞进
Distinct,得先Select(s => s?.Trim())再去重,否则"a "和"a"还是两个元素 - 慎用
StringComparer.CurrentCulture:它依赖线程当前区域设置,服务器部署时可能行为不一致
DistinctBy 是更安全的替代方案(.NET 6+)
比起手写比较器或 GroupBy,DistinctBy 更直观、性能更好,且天然支持多字段组合去重。
典型用法:
- 单字段:
items.DistinctBy(x => x.Name) - 多字段:
items.DistinctBy(x => new { x.Category, x.Priority }) - 注意:返回的是新序列,原集合不变;且
DistinctBy不保证稳定(即保留首次出现的元素),但实际目前实现确实是保留第一个,别依赖这点 - 如果字段可能为 null,lambda 表达式里要主动处理,比如
x => x.Name ?? string.Empty,否则可能抛NullReferenceException
性能差异:Distinct vs GroupBy vs 自定义 HashSet
数据量大时(比如 >10 万条),默认 Distinct() 性能不如手动用 HashSet 缓存键值再过滤,尤其当去重逻辑复杂时。
对比要点:
-
Distinct()内部用Set<t></t>,但每次都要调用GetHashCode和Equals,开销固定 -
GroupBy构建完整分组结构,内存占用高,纯去重场景属于杀鸡用牛刀 - 手动
HashSet<tkey></tkey>+Where最快,但代码变长,例如:var seen = new HashSet<int>(); list.Where(x => seen.Add(x.Id))</int> - 如果只是临时脚本或数据量小(DistinctBy 或
Distinct+ 比较器
真正容易被忽略的是:去重逻辑一旦涉及多个属性或嵌套对象,GetHashCode 的实现必须严格匹配 Equals,漏掉一个字段或用了不同判断条件,就会导致“看起来去重了,其实没去干净”。


















