LINQ 语言集成查询 (Language Integrated Query)

INQ 是 语言集成查询 (Language Integrated Query) 的缩写,它允许你直接在 C# 里用声明式的语法来查询和转换各种数据源(如集合、数据库、XML),而不用写循环。

LINQ 的写法主要分两种,它们的核心是一样的,只是语法不同。

1. 两种语法风格

  • 查询语法 (Query Syntax):很像 SQL,更接近自然语言,可读性好。

  • 方法语法 (Method Syntax / Lambda 语法) :通过扩展方法和 Lambda 表达式 串联调用,功能更全,也更常用。

假设我们有一个学生列表,要从中找出成绩大于80分的学生名字:

csharp

复制代码
// 准备数据
List<Student> students = new List<Student>()
{
    new Student { Name = "张三", Score = 85 },
    new Student { Name = "李四", Score = 70 },
    new Student { Name = "王五", Score = 92 }
};

// 1. 查询语法 (Query Syntax)
var queryResult = from s in students
                  where s.Score > 80
                  orderby s.Name
                  select s.Name;

// 2. 方法语法 (Method Syntax / Lambda)
var methodResult = students
                    .Where(s => s.Score > 80) // 过滤
                    .OrderBy(s => s.Name)     // 排序
                    .Select(s => s.Name);    // 投影

这两种写法都会被编译器翻译成完全相同的 IL 代码,结果也完全一样

2. 核心操作符分三类

LINQ 的操作就像一条数据处理的流水线,理解它的关键在于"延迟执行"。

第一步:获取数据

万事俱备,先得有数据源。任何实现了 IEnumerable<T> 接口的对象都可以用 LINQ

1. IEnumerable<T> 到底是什么?

它是一个极其简单的接口,里面只定义了一个核心功能:获取枚举器(GetEnumerator) 。 大白话来说:只要一个类实现了 IEnumerable<T>,就等于向 C# 编译器发誓:"我肚子里的数据,是排着队的一串序列,你可以用 foreach 循环把它们挨个拿出来看!"

2. 底层逻辑(为什么 LINQ 要挂在它身上?)

因为 LINQ 的本质就是"遍历数据 -> 制定规则 -> 提取结果"。 既然所有能被遍历的集合都实现了 IEnumerable<T>,微软的架构师就非常聪明地把 LINQ 所有的扩展方法(this IEnumerable<T> source)都挂载到了这个最基础的接口上。这就是为什么你的数组和 List 都能点(.)出 WhereOrderBy 的原因。

都有谁实现了 IEnumerable<T>?(家族大盘点)

除了你已经知道的 List<T>(动态列表)和 T[](数组),C# 中几乎所有用来"装东西"的集合都实现了它:

  1. Dictionary<TKey, TValue> (字典)

    • 极度常用!它里面装的是键值对。当你用 LINQ 遍历字典时,你拿到的每一个元素是一个 KeyValuePair<TKey, TValue> 对象。
  2. HashSet<T> (哈希集合)

    • 它的特点是绝对去重查找极快。如果你有一堆数据需要去重,放进这里面,它同样支持 LINQ。
  3. Queue<T> (队列) / Stack<T> (栈)

    • 队列是"先进先出"(排队买票),栈是"后进先出"(叠盘子)。它们也能用 LINQ。
  4. IQueryable<T> (数据库查询)

    • 这个极其关键!它是 IEnumerable<T> 的"升级版子接口"。Entity Framework Core 操作数据库时用的就是它。它能把你的 LINQ 翻译成真实的 SQL 语句发给数据库。
  5. String (字符串)

    • 没想到吧?字符串本质上是一个字符数组。所以 string 其实实现了 IEnumerable<char>。这意味着你可以直接对一个字符串用 LINQ,比如:"Hello".Where(c => c == 'l')
易混淆知识点打擂台(集合的阶级森严)

很多新手分不清 IEnumerable<T>ICollection<T>IList<T>。其实它们是"爷爷、爸爸、儿子"的继承关系,功能越来越强:

接口级别 核心能力 包含的方法/属性 大白话比喻
爷爷:IEnumerable<T> 只能看(遍历) GetEnumerator() 博物馆的玻璃展柜,你只能挨个看,不能摸。
爸爸:ICollection<T> 能看,且能增删、能数个数 多了 .Count, .Add(), .Remove() 你自己的储物箱,你可以往里扔东西,也能拿出来,也能数数。
儿子:IList<T> 能看、能增删,且能按位置精准插拔 多了索引器 [index], .Insert() 带有编号的中药柜,你可以精准指着第 3 个抽屉说:"把里面的东西换掉"。

架构设计边界指南:

  • ✅ 适用场景: 当你写一个函数,只需要读取 数据时,参数类型尽量写 IEnumerable<T>。这叫"宽进",无论别人传数组还是 List 给你,你的函数都不用改。

  • 🚫 禁止场景: 如果你的函数内部需要往集合里 Add() 添加元素,绝对禁止 将参数写成 IEnumerable<T>(因为它没有 Add 方法),此时应该写 ICollection<T>IList<T>

第二步:数据转换 (最重要)

这条流水线的核心是惰性求值 。像 WhereSelectOrderBy 这些方法,你调用时它不会立即执行 ,只是搭了个查询管道。只有到你真正去用数据(比如 foreach 遍历或调用 ToList())时,它才会从头到尾把数据处理一遍。

  • 过滤 WhereWhere(s => s.Score > 80),筛出满足条件的。

  • 投影 SelectSelect(s => new { s.Name, s.Score }),把元素转换成想要的形状,还支持匿名类型。

  • 排序 OrderBy/ThenByOrderBy(s => s.Score).ThenBy(s => s.Name),先按分数再按姓名排序。降序是 OrderByDescending

  • 分组 GroupByGroupBy(s => s.Grade),按年级分组,会把数据变成"钥匙-一组值"的结构。

  • 连接 Joinstudents.Join(classes, s => s.ClassId, c => c.Id, (s, c) => ...),和 SQL 的连表一样,关联两个集合。

  • 去重 DistinctDistinct(),去除重复项。

第三步:立即执行求值

这些方法会立刻触发查询"流水线",得到最终结果。

  • 转为集合.ToList().ToArray().ToDictionary()

  • 聚合计算.Count().Sum().Average().Max().Min()

  • 取单个元素.First()(无则抛异常)、.FirstOrDefault()(无则返回默认值 null/0)、.Single()(确保只有一个)。OrDefault 系列非常常用。

五、实战对象举例(最能理解)Linq语句方法后都是什么λ类型表达式

假如你有一个学生类:

复制代码
class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

List<Student> list = ...;

1. Where(判断)

复制代码
list.Where(s => s.Age > 18);
// Func<Student, bool>  输入学生 → 输出 bool

2. OrderBy(提取排序键)

复制代码
list.OrderBy(s => s.Age);
// Func<Student, int>   输入学生 → 输出 int(年龄)

3. ThenBy(提取次要排序键)

复制代码
list.OrderBy(s => s.Class).ThenBy(s => s.Score);
// 都是 Func<Student, 键类型>

4.Select 里的 Lambda 类型是:

Func<TSource, TResult>

六、最终超级总结(必背)

  • Where :Lambda = 条件Func<T, bool>
  • OrderBy / ThenBy :Lambda = 排序键Func<T, TKey>

你现在已经彻底吃透 LINQ + 泛型 + Lambda 底层原理了!

七、LINQ 四大金刚 Lambda 类型总结(超级完整版)

这张表背下来,LINQ 彻底毕业!

表格

方法 Lambda 类型 作用 输入 → 输出
Where Func<T, bool> 筛选 元素 → true/false
OrderBy/ThenBy Func<T, TKey> 排序 元素 → 排序键
Select Func<T, TResult> 投影 / 转换 元素 → 任意新结果
First/Any/Count Func<T, bool> 判断 元素 → true/false
相关推荐
zlinear数据采集卡18 小时前
数据采集卡从入门到精通(9):分辨率与精度——16位卡不等于1/65536的精度
开发语言·数据库·fpga开发·开源·c#
深澈Vincel21 小时前
我用 C# + WebView2 做了一个针对“流氓软件”的“一键清理工具“:架构实践与踩坑记录
c#
zlinear数据采集卡1 天前
数据采集卡从入门到精通(7):流水线型ADC——级级接力,高速与高精的平衡术
开发语言·arm开发·嵌入式硬件·fpga开发·c#
阿部多瑞 ABU1 天前
告别手工核图:基于 .NET + MuPDFCore 的 CAD 等轴测 PDF 材料表提取与新旧版本对比实战
后端·算法·ui·pdf·c#
条形码D1 天前
DRG综合管理分析系统-目前已经支持CHS-DRG3.0
c#·drg·3.0分组器·drg分组器·chs-drg3.0分组器
Alex Gram2 天前
数据库同步工具PanguSync图文教程
java·mysql·postgresql·sqlserver·c#·数据库同步软件·数据库同步工具
头茬韭菜2 天前
第 10 篇:「Fluss 实战案例集」—— 完整解决方案与最佳实践
c#·linq·fluss
淡海水2 天前
01-05-运行时-JIT优化全景-内联去虚拟化边界检查消除
windows·c#·虚拟化·jit·内联·边界检查消除
波波0072 天前
C# 15 重磅新特性: 带标签 break 与 continue:重新定义嵌套循环控制流
服务器·前端·c#
wujian83113 天前
怎么用千问生成word文档:从「格式崩」到「一键过」,AI导出鸭打通最后半厘米
人工智能·ai·c#·word·豆包·deepseek·ai导出鸭