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
相关推荐
吴可可1231 小时前
C++与C#版Teigha样条离散化差异解析
c++·算法·c#
JaydenAI2 小时前
[MAF预定义ChatClient中间件-03]CachingChatClient——利用缓存省钱(Token)省时间
ai·c#·agent·caching·maf·chatclient中间件
曹牧2 小时前
C#:List<T>.ForEach(Action<T> action)
c#
海盗12342 小时前
C#中PDF操作-QuestPDF介绍和使用教程
pdf·c#
影寂ldy2 小时前
C#数组的高级方法
开发语言·c#
曹牧2 小时前
C#:基类中定义泛型方法
java·开发语言·c#
游乐码2 小时前
c#基础(七)延迟函数
开发语言·unity·c#·游戏引擎
魔法阵维护师2 小时前
从零开发游戏需要学习的c#模块,第二十六章(多种敌人与基础 AI)
学习·游戏·c#
思麟呀2 小时前
在C++基础上理解CSharp-4
开发语言·jvm·c++·c#