distinctby 是 .net 6+ 新增的 linq 方法,用于按指定键去重并保留首次出现元素;需目标框架为 net6.0 或更高,否则需用 groupby + select 替代。

DistinctBy 是啥?它不在 .NET Framework 里
你写 DistinctBy 报错,大概率是因为项目没装 System.Linq 的扩展包——这个方法压根不是 C# 原生 LINQ 的一部分,而是 .NET 6+ 才加入的官方 API。如果你用的是 .NET 5 或更早版本,或者项目目标框架是 netcoreapp3.1、net472,那它根本不存在。
常见错误现象:'IEnumerable<t>' does not contain a definition for 'DistinctBy'</t>
- 确认项目 SDK:在
.csproj中检查<targetframework></targetframework>是否为net6.0或更高(如net8.0) - 如果不是,要么升级框架,要么改用
GroupBy+Select(g => g.First())替代(性能略差,但兼容性好) - 别试图用 NuGet 安装“LINQ 扩展包”来补这个方法——它不靠第三方包,只靠框架版本
DistinctBy 的基本用法和参数陷阱
它接受一个 key selector 函数,按返回值去重,保留首次出现的元素。关键点在于:key selector 返回的类型必须能被默认比较器正确识别,否则可能“看似去重失败”。
比如对 List<person></person> 按 Name 去重:
var unique = people.DistinctBy(p => p.Name);
- 如果
Name是string,没问题;但如果是自定义类(比如Address),且没重写Equals/GetHashCode,就会把所有对象都视为不同——DistinctBy内部用的是EqualityComparer<tkey>.Default</tkey> - 不能传 null:如果
p.Name可能为null,而 key 类型是不可空引用类型(如string?开启了 nullable context),需显式处理,否则运行时可能抛NullReferenceException - 不支持多字段组合直接写法(比如
(p.FirstName, p.LastName)元组在 .NET 6+ 是 OK 的,但 .NET 7+ 才真正稳定支持元组比较;稳妥起见建议用匿名对象或Tuple.Create)
DistinctBy 和 GroupBy 性能与语义差异
表面上都能“按某字段取唯一”,但底层逻辑完全不同:前者是流式一次遍历 + 哈希缓存 key,后者是分组再取首项,内存占用和执行时机都不同。
-
DistinctBy是延迟执行,但内部会缓存所有见过的 key(不是整个元素),空间复杂度 O(n),时间接近 O(n) -
GroupBy(x => x.Id).Select(g => g.First())会构建完整分组结构,内存开销更大,尤其在大数据集上明显 - 语义上,
DistinctBy明确表达“我要按这个 key 去重”,而GroupBy是“先分组再选”,意图模糊,可读性差一点 - 如果后续还要做聚合(比如统计每组数量),那就别硬套
DistinctBy,直接用GroupBy更自然
DistinctBy 在 IQueryable(比如 EF Core)里不能用
EF Core 8 支持部分 DistinctBy 翻译成 SQL(仅限简单属性路径,如 x.Status),但多数情况仍会触发客户端评估(Client Evaluation),也就是把全部数据拉到内存再执行去重——这很危险,尤其表很大时。
- 查数据库前就加
DistinctBy?大概率报错或静默转客户端执行 - 用
AsNoTracking()也救不了——关键看 EF 能不能把 lambda 翻译成 SQL - 真要服务端去重,优先考虑原生 SQL 的
DISTINCT ON(PostgreSQL)或子查询 +ROW_NUMBER()(SQL Server / SQLite) - 实在要 LINQ 风格,EF Core 7+ 推荐用
Distinct()配合投影到匿名类型或记录(record),比DistinctBy更可控
最容易被忽略的一点:DistinctBy 的 key selector 一旦涉及导航属性(比如 p.Order.Customer.Name),几乎必然客户端执行,连警告都不会给——得自己盯紧日志里的 Microsoft.EntityFrameworkCore.Query 输出。











