LINQ 概述与查询语法
本教程共 100 篇 · 第 67 篇 · 更新于 2026-07-31 · 约 11 分钟阅读
67. LINQ 概述与查询语法
本节目标:学完你能用 LINQ 的查询语法筛选集合,并理解「为什么查询不会立刻执行」,以及它适合在哪些场景代替手写循环。
你有没有觉得,从集合里挑数据要写一堆 for 循环很啰嗦?LINQ(Language Integrated Query,语言集成查询)就是来解决这件事的:把「查询」变成 C# 语言的一等公民。
什么是 LINQ
LINQ 是一套统一的查询能力。无论数据来自 List<T>、数组、还是数据库,都能用相同的语法去查。它最大的好处是:类型安全、有智能提示、写法统一。
用一个类比:以前查集合像「手动翻抽屉找东西」,LINQ 像「给抽屉装了个搜索引擎」——你用一句声明式的话描述「要什么」,具体怎么翻交给运行时。这种「描述意图而非步骤」的写法,称为**声明式(declarative)**编程,和一步步写 for 的「命令式」相对。
NoteLINQ 有两种写法:本章讲的「查询语法」(像 SQL),以及下一章的「方法语法」(点号链式)。两者底层等价,没有性能差异,选顺手的即可。实际项目里方法语法更常见,但查询语法在
join、group时更直观。
查询语法长什么样
查询语法用 from...in...where...select 关键字,读起来像 SQL。先从最简单的筛选开始:
List<int> numbers = [1, 2, 3, 4, 5, 6];
var even = from n in numbers
where n % 2 == 0
select n;
foreach (var n in even)
{
Console.WriteLine(n);
}
输出:
2
4
6
要点拆解:
from n in numbers:numbers是数据源,n是「范围变量」,代表当前元素。where n % 2 == 0:过滤条件,只留偶数。select n:决定返回什么,这里是元素本身。
投影成新形状
select 不仅能返回原元素,还能算出新的东西。比如把每个数平方:
List<int> numbers = [1, 2, 3];
var squares = from n in numbers
select n * n;
foreach (var s in squares)
{
Console.WriteLine(s);
}
输出:1 4 9。select 后面可以是任意表达式,这就叫「投影(projection)」。
排序也有专属子句
查询语法里用 orderby 排序,比方法语法更直观:
List<int> numbers = [3, 1, 4, 1, 5, 9, 2];
var sorted = from n in numbers
orderby n
select n;
foreach (var n in sorted)
{
Console.WriteLine(n);
}
输出:1 1 2 3 4 5 9。orderby n descending 则降序。
用 let 给中间结果起名
当过滤或投影的表达式比较复杂,反复写会又长又难读。查询语法提供 let 子句,给「算出来的中间值」起个名字,后面复用:
List<int> numbers = [3, 4, 5, 6];
var result = from n in numbers
let squared = n * n
where squared > 15
select $"{n} 的平方是 {squared}";
foreach (var s in result)
{
Console.WriteLine(s);
}
输出:
4 的平方是 16
5 的平方是 25
6 的平方是 36
let squared = n * n 先把平方算好,where 和 select 直接用它,逻辑一目了然。方法语法里没有 let 的对应物,得用嵌套或先 Select 再 Where,这也是查询语法偶尔更清爽的地方。
LINQ 能查哪些数据源
LINQ 的「统一」体现在:只要数据源实现了 IEnumerable<T>(查内存对象,叫 LINQ to Objects),就能用同一套语法。而通过不同的「提供程序(provider)」,同一套写法还能延伸到:
- LINQ to XML:查 XML 文档;
- LINQ to Entities(Entity Framework):查数据库,写法不变,运行时翻译成 SQL。
也就是说,你学会了 from...where...select,这套直觉在查数据库时也能直接用,不用再学一套新语言。本章只聚焦内存里的集合(LINQ to Objects),其他后端知道「语法通用」即可。
延迟执行:最关键的直觉
LINQ 查询有一个反直觉但极重要的特性:定义查询时并不执行,只有遍历时才真正算。
List<int> numbers = [1, 2, 3];
var query = from n in numbers
where n > 1
select n;
numbers.Add(4); // 定义查询之后才修改数据源
foreach (var n in query)
{
Console.WriteLine(n);
}
输出:
2
3
4
注意!4 也被查出来了,尽管它是在查询定义之后才加入的。因为 query 只是「查询方案」,真正跑是在 foreach 那一刻。
Tip想立刻拿到结果、不再受后续改动影响?调用
ToList()或ToArray()把查询「落地」:var result = query.ToList();。
为什么设计成延迟执行
第一,省性能。如果最终只用前几条,延迟执行能避免白算后面的。第二,查询可以组合、复用。第三,配合数据源变化(如实时流),每次遍历都能拿到最新状态。
Warning延迟执行也有坑:如果在
foreach之前改了数据源,结果会跟着变——这未必是你想要的。另外,同一个查询变量每遍历一次都会重新执行一遍。若数据量大、又要在多处反复用同一结果,记得用ToList()缓存一次,别在循环里反复foreach同一个查询。
适用场景:何时用 LINQ,何时手写循环
LINQ 不是万能锤,知道何时用很重要:
- 适合用 LINQ:筛选、投影、排序、分组、聚合这类「对集合做变换」的操作;代码可读性明显更好,且不易写错循环边界。
- 也适合:把多步变换串成一条管道(下一章方法语法见得最多),意图清晰。
- 手写
for更合适:循环体里有复杂的「副作用」逻辑(比如边循环边调用多个服务、修改多个外部状态);或者性能极端敏感的热路径,且你已经实测 LINQ 成了瓶颈。 - 注意:LINQ 强调「不修改数据源、产出新序列」。如果你本就想原地改集合,用
for/foreach更直接。
Note经验法则:凡是「从集合 A 得到集合 B」的纯变换,优先考虑 LINQ;凡是「为了做事(副作用)而遍历」,用普通循环。把 LINQ 当「查询语言」,把循环当「执行动作」。
与其它写法对比
| 写法 | 可读性 | 出错概率 | 适合 |
|---|---|---|---|
for/foreach 手撸 | 中 | 较高(边界、索引易错) | 有副作用、需精细控制 |
查询语法 from...where...select | 高(像 SQL) | 低 | join/group/let 场景 |
方法语法 Where().Select() | 高(流畅) | 低 | 多数日常查询(下章) |
三种都能达成目标,LINQ 两种语法主要在「少写样板、类型安全」上胜出。
新手容易踩的坑
第一,忘记遍历。只写查询不 foreach、不 ToList(),什么都不会发生,因为根本没执行。
第二,以为 select 一定会立即算。它只是描述「要什么」,执行时机在遍历时。
第三,查询变量可以反复遍历,但每遍历一次都会重新执行一遍。数据量大时,记得用 ToList() 缓存,别在循环里反复查同一句。
第四,误以为 LINQ 改了原集合。from...where...select 只产出新序列,原 numbers 纹丝不动。
常见疑问解答
问:查询语法和方法语法哪个更快? 没有本质差别。编译器会把查询语法翻译成方法语法再编译,运行时是同一套实现。选可读性高的那个,别为了「快」纠结语法。
问:延迟执行会不会让程序变慢?
不会,反而常更快(按需计算、少算无用部分)。真正要小心的是「同一查询被反复遍历多遍」导致重复计算,那种情况 ToList() 缓存即可。
问:let 和 select 里直接算有什么区别?
let 把中间结果命名后可在 where/select 里复用,避免重复计算同一表达式;直接在 select 里算则每次都现算。复杂表达式用 let 更清楚。
小结
LINQ 把查询变成语言的一部分。查询语法用 from/where/select/orderby/let 关键字,读起来像 SQL 且类型安全;它适用于「从集合得到新集合」的纯变换,而在有副作用或极致性能场景用手写循环更合适。务必记住延迟执行:查询是方案,遍历才动手。下一章看另一种写法——方法语法,以及支撑它的扩展方法原理。