使用Where筛选时需先判空防NullReferenceException;Select中避免耗时操作,应预加载数据;FirstOrDefault对值类型返回默认值而非null;调试时ToList()可固化结果但生产环境慎用。

用 Where 筛选数据时,别直接写 null 判断条件
很多新手在用 Where 过滤集合时,会这样写:list.Where(x => x.Name != null && x.Name.StartsWith("A"))。这看起来没问题,但一旦 x 本身是 null(比如 List<Person> 里插了 null 元素),运行时就抛 NullReferenceException。
正确做法是先确保对象非空,再访问属性:
- 如果确定集合不含
null,可加注释说明:// Assume no null elements - 否则显式过滤:
list.Where(x => x != null).Where(x => x.Name?.StartsWith("A") == true) - 注意
Name?.StartsWith("A") == true而不是== "True"或直接用&&,避免null返回null导致逻辑错误
Select 投影时,别在表达式里做耗时操作
Select 是延迟执行的,但每次枚举都会重新计算表达式体。如果在里面调用数据库查询、文件读取或复杂计算,性能会断崖式下跌。
常见错误示例:items.Select(x => new { Id = x.Id, Desc = GetDescriptionFromDb(x.Id) }) —— 每次遍历都查一次库。
- 提前把依赖数据查出来,用字典缓存:
var descMap = db.Descriptions.ToDictionary(d => d.Id); - 再投影:
items.Select(x => new { x.Id, Desc = descMap.GetValueOrDefault(x.Id) }) - 如果必须动态计算,且数据量小,至少用
.ToList()触发一次执行,避免重复计算
用 FirstOrDefault 找不到时返回 default(T),不是 null
对引用类型,FirstOrDefault() 找不到确实返回 null;但对值类型(如 int、DateTime),它返回的是该类型的默认值(0、DateTime.MinValue),不是 null —— 即使你用了可空类型,也要看泛型参数是不是 T?。
典型陷阱:list.Select(x => x.Age).FirstOrDefault(x => x > 100),如果 Age 是 int,找不到就返回 0,而不是你想判断的“未找到”。
- 明确需要区分“没找到”和“找到值为 0”,就用可空类型:
list.Select(x => (int?)x.Age).FirstOrDefault(x => x > 100) - 或者改用
SingleOrDefault+ 检查是否等于default,但注意它会在多个匹配时抛异常 - 更安全的写法是:先
Where再Take(1),然后用Any()和First()分开判断
调试 LINQ 表达式时,ToList() 不只是“转成列表”
很多人加 ToList() 只是为了让断点能停住、能看到结果。但它真正的作用是强制立即执行,并把结果固化到内存中。这意味着后续对原集合的修改,不会影响这个 List<T> 的内容;而没加 ToList() 的 IQueryable 或 IEnumerable,每次访问都可能重新执行整个链式操作。
- 调试阶段可以放心加:
var result = query.ToList();,然后在result上设断点观察 - 但上线后别无脑加——尤其是对大集合或数据库查询,
ToList()会一次性拉取全部数据,可能 OOM 或拖慢响应 - 如果只想看前几条,用
Take(5).ToList();如果要确认是否为空,用Any()比Count() > 0高效得多
Where 后直接改属性,下次查询可能因缓存或状态不一致出错。别只盯着语法,得看清它背后是内存迭代还是 SQL 翻译。


















