首页 / C# 入门教程 / LINQ 概述与查询语法

C# 入门教程

LINQ 概述与查询语法

本教程共 100 篇 · 第 67 篇 · 更新于 2026-07-31 · 约 11 分钟阅读

C#C# 入门教程编程语言LINQ查询语法延迟执行

67. LINQ 概述与查询语法

本节目标:学完你能用 LINQ 的查询语法筛选集合,并理解「为什么查询不会立刻执行」,以及它适合在哪些场景代替手写循环。

你有没有觉得,从集合里挑数据要写一堆 for 循环很啰嗦?LINQ(Language Integrated Query,语言集成查询)就是来解决这件事的:把「查询」变成 C# 语言的一等公民。

什么是 LINQ

LINQ 是一套统一的查询能力。无论数据来自 List<T>、数组、还是数据库,都能用相同的语法去查。它最大的好处是:类型安全、有智能提示、写法统一。

用一个类比:以前查集合像「手动翻抽屉找东西」,LINQ 像「给抽屉装了个搜索引擎」——你用一句声明式的话描述「要什么」,具体怎么翻交给运行时。这种「描述意图而非步骤」的写法,称为**声明式(declarative)**编程,和一步步写 for 的「命令式」相对。

Note

LINQ 有两种写法:本章讲的「查询语法」(像 SQL),以及下一章的「方法语法」(点号链式)。两者底层等价,没有性能差异,选顺手的即可。实际项目里方法语法更常见,但查询语法在 joingroup 时更直观。

查询语法长什么样

查询语法用 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 numbersnumbers 是数据源,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 9select 后面可以是任意表达式,这就叫「投影(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 9orderby 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 先把平方算好,whereselect 直接用它,逻辑一目了然。方法语法里没有 let 的对应物,得用嵌套或先 SelectWhere,这也是查询语法偶尔更清爽的地方。

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 不是万能锤,知道何时用很重要:

  1. 适合用 LINQ:筛选、投影、排序、分组、聚合这类「对集合做变换」的操作;代码可读性明显更好,且不易写错循环边界。
  2. 也适合:把多步变换串成一条管道(下一章方法语法见得最多),意图清晰。
  3. 手写 for 更合适:循环体里有复杂的「副作用」逻辑(比如边循环边调用多个服务、修改多个外部状态);或者性能极端敏感的热路径,且你已经实测 LINQ 成了瓶颈。
  4. 注意: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() 缓存即可。

问:letselect 里直接算有什么区别? let 把中间结果命名后可在 where/select 里复用,避免重复计算同一表达式;直接在 select 里算则每次都现算。复杂表达式用 let 更清楚。

小结

LINQ 把查询变成语言的一部分。查询语法用 from/where/select/orderby/let 关键字,读起来像 SQL 且类型安全;它适用于「从集合得到新集合」的纯变换,而在有副作用或极致性能场景用手写循环更合适。务必记住延迟执行:查询是方案,遍历才动手。下一章看另一种写法——方法语法,以及支撑它的扩展方法原理。