first()在集合为空时抛invalidoperationexception,firstordefault()返回默认值;二者语义不同:前者断言“必须存在”,后者处理“可能不存在”场景,误用会导致500错误或逻辑错误。

First 和 FirstOrDefault 用错会直接抛异常
当集合为空时,First() 立刻抛出 InvalidOperationException:“序列中不包含任何元素”;而 FirstOrDefault() 返回类型的默认值(比如 null 对于引用类型,0 对于 int)。这不是“更安全”的替代,而是语义完全不同:你是在明确要求“必须存在第一个”,还是“可能没有,那就给个默认”。
常见错误现象:
• 在 Web API 返回分页列表前取 First(),但数据库查不到数据 → 500 错误
• 对 IEnumerable<int></int> 调用 FirstOrDefault() 后直接做数学运算,没检查是否为 0(实际是空集合导致的假 0)
- 如果业务逻辑依赖“一定有首个元素”(例如配置项非空校验),就用
First()—— 异常反而是好事,暴露数据问题 - 如果属于“尝试获取,失败可降级”(比如查用户头像 URL,没找到就用默认图),必须用
FirstOrDefault() - 对可空引用类型(如
string),FirstOrDefault()返回null;对不可空值类型(如int),返回0,不是default(int?)—— 注意别和First()??.ToString()混淆
带条件的 First(x => ...) 和 FirstOrDefault(x => ...) 行为一致
加谓词后,两者依然遵循“找不到就炸” vs “找不到就返默认”的规则。但容易被忽略的是:谓词执行时机和短路逻辑。
使用场景:
• 查找状态为 Active 的第一个订单
• 从日志集合中取最近一条错误日志
- 谓词只在遍历到每个元素时才执行,不会预扫描整个集合 —— 所以即使集合很大,只要第一个匹配项靠前,性能就很好
- 如果用
Where(...).First(),等价于First(...),但多一次迭代开销(先过滤再取首);直接用带谓词的First(...)更高效 - 注意谓词里别有副作用(比如修改外部变量),因为 LINQ 是延迟执行,且可能因优化不执行全部元素
FirstOrDefault 返回 default(T) 时,值类型判断要小心
对 int、DateTime、Guid 这类值类型,FirstOrDefault() 返回的是语言定义的 default(T),不是“未设置”状态。这意味着你无法区分“找到了值为 0 的元素”和“根本没找到”。
常见错误现象:
• list.FirstOrDefault(x => x.Price == 0) 和 list.FirstOrDefault(x => x.Id == 123) 都可能返回 0,但含义天差地别
• 把 DateTime 的 default(DateTime)(即 0001-01-01)当成有效时间参与计算
- 值类型场景下,优先考虑用
FirstOrDefault()+ 显式判断是否等于default(T),但前提是default(T)在业务中不可能是合法值 - 更稳妥的做法是改用
SingleOrDefault()或结合Any()判断,或者直接用可空类型:比如list.Select(x => (int?)x.Value).FirstOrDefault(),这样返回null就明确表示“没找到” - 对
Guid,default(Guid)是全 0 的 GUID,业务中几乎不会合法出现,所以相对安全;但对int和bool就非常危险
性能和 IQueryable 场景下行为可能完全不同
在内存集合(List<t></t>)上,First() 和 FirstOrDefault() 都是 O(1) 平均复杂度(找到第一个匹配就停);但在 EF Core 的 IQueryable 上,它们最终生成的 SQL 是一样的 —— 都是 SELECT TOP(1) ...。区别只在客户端处理逻辑。
使用场景:
• EF Core 查询数据库取单条记录
• Dapper 返回 IEnumerable<t></t> 后再用 LINQ 处理
- EF Core 中用
First()或FirstOrDefault()不影响 SQL 性能,但会影响异常路径 ——First()会让 EF 尝试把空结果集映射成异常,而FirstOrDefault()接收 null/默认值 - 如果查询本身带
OrderBy,务必确认索引是否存在,否则First()可能触发全表排序再取 Top 1,很慢 - 不要为了“看起来安全”而在数据库查询里无脑用
FirstOrDefault(),如果业务上这条记录必须存在(比如根据主键查用户),First()的异常反而帮你守住契约










