district() 默认按引用去重,不比较字段值;distinctby()(.net 6+)支持按字段去重,groupby().select() 是旧版替代方案;字符串去重需注意大小写和空格处理。

对自定义类直接调用 District() 几乎肯定不会按字段去重——它只比内存地址,哪怕两个对象字段一模一样也会被当成不同项。
为什么 District() 对 Person 类“没反应”
默认比较器走的是 ReferenceEquals(),不是字段值比较。调试时看到 Name 和 Age 完全相同,但长度不变、断点里元素还在,就是这个原因。
- 值类型(
int、DateTime)和已重写Equals()的引用类型(如string)可直接用 - 自定义类必须显式提供比较逻辑,否则行为不可预测
- 常见错误现象:
District()调用后集合长度不变,或“明明一样却还在”
DistrictBy() 是最省事的替代方案(.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() 曲线救国
没有 DistrictBy() 就没法优雅地按字段去重,GroupBy(x => key).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()) - 性能上不如
DistrictBy()(要建完整分组结构),但开发效率高、不易出错、兼容性好
字符串去重容易踩的坑
District() 对 string 默认使用 StringComparer.Ordinal,区分大小写、不忽略首尾空格。
- 常见错误:以为
"ABC"和"abc"会被合并 → 实际不会 - 忽略大小写:传
StringComparer.OrdinalIgnoreCase,如strings.Distinct(StringComparer.OrdinalIgnoreCase) - 同时去掉空格再比较:不能直接塞进
District(),得先Select(s => s?.Trim())再去重,否则"a "和"a"还是两个元素 - 慎用
StringComparer.CurrentCulture:它依赖线程当前区域设置,服务器部署时可能行为不一致
真正复杂的地方不在语法,而在 key selector 的健壮性——比如字段为 null 时是否抛异常、多字段组合时匿名类型字段名大小写是否统一、旧框架下 GroupBy 分组键是否真能哈希——这些细节一旦漏掉,去重就变成“看似生效实则漏数据”。











