结论:.distinct()对引用类型默认按内存地址去重,自定义类需显式提供比较逻辑;.net 6+推荐用.distinctby()按字段去重,旧版本可用groupby+select替代。

直接说结论:对引用类型(比如 List<person></person>)调用 .Distinct() 默认不会按字段去重,它只比内存地址——哪怕两个对象内容一模一样,也会被当成不同元素保留下来。
为什么 .Distinct() 对自定义类“没去重”
因为默认比较器走的是引用比较(ReferenceEquals),不是字段值比较。你调试时看到两个 Person 的 Name 和 Age 完全相同,但 .Distinct() 仍返回全部元素,就是这个原因。
- 值类型(
int、string、DateTime等)可直接用,安全 -
string是引用类型但已重写Equals,所以List<string>.Distinct()</string>没问题 - 自定义类必须显式提供比较逻辑,否则行为不可预测
- 常见错误现象:
.Distinct()调用后集合长度不变,或断点里发现“明明一样却还在”
.DistinctBy() 是最省事的替代方案(.NET 6+)
它是专为“按字段去重”设计的,语义清晰、性能好、代码短。但前提是你的项目目标框架是 net6.0 或更高(比如 net8.0)。
- 报错
'IEnumerable<t>' does not contain a definition for 'DistinctBy'</t>→ 检查.csproj中的<targetframework></targetframework>,不是版本问题别硬装 NuGet 包 - 单字段去重:
list.DistinctBy(x => x.Id)或list.DistinctBy(x => x.Name) - 多字段组合:
list.DistinctBy(x => new { x.Category, x.Priority })(匿名类型自动支持结构相等) - 字段可能为
null?写成x => x.Name ?? string.Empty,避免NullReferenceException - 注意:它不保证“稳定去重”,虽然当前实现保留首次出现项,但别在逻辑中依赖这点
老版本(.NET 5 及以下)只能靠 GroupBy 曲线救国
没有 DistinctBy 就没法优雅地按字段去重,GroupBy + Select(g => g.First()) 是最常用、最稳妥的替代写法。
- 按
CategoryId取首个:list.GroupBy(x => x.CategoryId).Select(g => g.First()).ToList() - 想取“最新”的(比如时间最大的):
g.OrderByDescending(x => x.CreatedTime).First() - 多字段组合也一样:
users.GroupBy(u => new { u.FirstName, u.LastName }).Select(g => g.First()) - 性能上不如
DistinctBy(要建完整分组结构),但开发效率高、不易出错、兼容性好 - 别用
IEqualityComparer<t></t>手写——除非你要复用比较逻辑或做复杂规则(比如忽略空格/大小写)
字符串去重容易踩的坑
string 虽然能直接用 .Distinct(),但默认行为很“严格”:区分大小写、不忽略首尾空格、不处理 Unicode 规范化。
-
"ABC"和"abc"默认不会合并 → 改用.Distinct(StringComparer.OrdinalIgnoreCase) -
"a "和"a"默认算两个 → 先.Select(s => s?.Trim())再去重 - 慎用
StringComparer.CurrentCulture:它依赖线程当前区域设置,服务器部署时可能行为突变 - 如果字段是
string?且开启 nullable context,Distinct()不会帮你判空,得自己处理
真正复杂的去重逻辑(比如按拼音首字母、按数字前缀截断)不适合塞进 Distinct 或 DistinctBy,该抽方法就抽方法,别为了“一行写完”牺牲可读性和可维护性。











