LINQ 核心知识点整理
LINQ 是.NET Core 中简化数据查询的技术,可操作普通.NET集合、EF Core 等数据源,其核心依赖Lambda表达式、扩展方法,提供方法语法和查询语法两种编写方式,以下是核心知识点的系统整理。
一、基础前置知识
1. 委托
委托是指向方法的类型,.NET 内置泛型委托可替代自定义委托,无需重复定义:
- Func<T, TResult> :有返回值,泛型最后一个参数为返回值类型,支持1~16个参数(如
Func<int, string>:int参数,string返回值)。 - Action:无返回值,支持1~16个参数(如
Action<int, int>:两个int参数,无返回值)。 - 委托可指向普通方法、匿名方法,是Lambda表达式的基础。
2. Lambda 表达式
(1)本质
匿名函数,用于创建委托,替代繁琐的匿名方法写法,运算符=>(读作goes to)为核心,格式:(输入参数) => 表达式/语句块。
(2)简化规则
- 可省略参数类型,编译器根据委托自动推断;
- 方法体仅一行代码+有返回值:省略花括号
{}和return; - 方法体仅一行代码+无返回值(Action委托):省略花括号
{}; - 仅一个参数:省略参数的圆括号
()。
(3)示例
// 原始匿名方法
Func<int, int, string> fn1 = delegate (int i, int j) { return $"和为{i+j}"; };
// Lambda 最简写法
Func<int, int, string> fn2 = (i, j) => $"和为{i+j}";
// 单个参数省略括号
Func<int, int> fn3 = i => i + 6;
// 无返回值省略花括号
Action<int, int> fn4 = (i, j) => Console.WriteLine($"和为{i+j}");
3. 隐式类型var
- 编译器根据变量初始化语句右侧表达式推断类型,编译后会确定具体类型,并非“无类型”;
- 常用于简化复杂类型声明(如泛型集合)、匿名类初始化。
4. 匿名类
- 无需显式定义类,直接封装一组只读属性,类型名由编译器生成;
- 必须用
var声明变量,初始化表达式不能为null/匿名函数。
var person = new { Name = "zhangsan", Age = 18 };
Console.WriteLine(person.Name + person.Age);
5. C# 扩展方法
LINQ 的底层实现依赖扩展方法,用于向现有类型动态添加新方法,无需修改原类型代码,定义规则:
- 扩展方法所在类必须是static静态类;
- 扩展方法本身必须是public + static;
- 第一个参数必须带
this关键字,后接要扩展的类型。
// 为string扩展转int方法
public static class ExpandMethod
{
public static int StringToInt(this string str)
{
return Convert.ToInt32(str);
}
}
// 调用
Console.WriteLine("3".StringToInt() + 3); // 输出6
- LINQ 为所有实现
IEnumerable<T>接口的集合提供了大量扩展方法,需引用System.Linq命名空间。
二、LINQ 核心扩展方法(方法语法)
所有方法均为IEnumerable<T>的扩展方法,支持链式调用(返回值多为IEnumerable<T>/派生接口),以下为最常用方法,均结合Lambda表达式使用。
1. 数据过滤:Where
- 作用:根据条件过滤集合,保留返回
true的元素; - 签名:
Where(Func<TSource, bool> predicate),predicate为过滤条件委托; - 替代传统
foreach + if的繁琐写法,代码更简洁。
// 过滤工资>3000且年龄<30的Person
IEnumerable<Person> filterList = list.Where(p => p.Salary > 3000 && p.Age < 30);
2. 数量统计:Count / LongCount
- Count:统计元素数量,返回
int,支持无参(统计总数)、有参(带过滤条件); - LongCount:统计超大集合(数量超
int最大值),返回long,用法与Count一致; - 链式调用:可先Where过滤再Count,结果与直接传过滤条件一致。
list.Count(); // 统计总数
list.Count(p => p.Salary > 2000); // 统计工资>2000的数量
list.Where(p => p.Salary > 2000).Count(); // 链式调用,结果同上
3. 存在性判断:Any
- 作用:判断集合中是否至少有一条满足条件的元素,返回
bool; - 效率远高于
Count(条件) > 0:Any找到第一条满足条件的元素即停止,Count会遍历全部; - 用法:无参(判断集合是否非空)、有参(带过滤条件)。
list.Any(p => p.Salary > 5000); // 是否有工资>5000的元素
list.Where(p => p.Salary > 5000).Any(); // 链式调用,结果同上
4. 获取单条数据:Single/SingleOrDefault/First/FirstOrDefault
四组方法均用于获取单条元素,核心区别在于对“无匹配元素”和“多匹配元素”的处理,均支持无参/有参(过滤条件),建议根据业务场景选择:
| 方法 | 无匹配元素 | 多匹配元素 | 适用场景 |
|---|---|---|---|
| Single | 抛出异常 | 抛出异常 | 确认有且仅有一条匹配数据 |
| SingleOrDefault | 返回类型默认值 | 抛出异常 | 确认最多一条匹配数据 |
| First | 抛出异常 | 返回第一条匹配 | 匹配数据多条,取第一条 |
| FirstOrDefault | 返回类型默认值 | 返回第一条匹配 | 匹配数据可无/多条,取第一条 |
注意:引用类型默认值为null,需用可空类型(?)接收,避免空引用。
// Single:有且仅有一条,否则抛异常
Person p1 = list.Single(p => p.Id == 1);
// FirstOrDefault:无匹配返回null,多匹配返回第一条
Person? p2 = list.FirstOrDefault(p => p.Age > 60);
if (p2 == null) Console.WriteLine("无匹配数据");
5. 排序:OrderBy/OrderByDescending + ThenBy/ThenByDescending
-
基础排序:
OrderBy:升序排序,返回IOrderedEnumerable<TSource>;OrderByDescending:降序排序,返回IOrderedEnumerable<TSource>;
-
多条件排序:基础排序后用
ThenBy(升序)/ThenByDescending(降序)追加条件,不可连续用OrderBy(会覆盖前序排序); -
排序条件可自定义(如对象属性、字符串最后一个字符等)。
// 先年龄升序,年龄相同则工资降序
var sortedList = list.OrderBy(p => p.Age).ThenByDescending(p => p.Salary);
// 先年龄降序,年龄相同则工资升序
var sortedList2 = list.OrderByDescending(p => p.Age).ThenBy(p => p.Salary);
// 按用户名最后一个字符降序
var sortedList3 = list.OrderByDescending(p => p.Name![p.Name!.Length - 1]);
6. 限制结果集:Skip + Take
用于分页查询,联合使用实现“跳过n条,取m条”,支持链式调用(先过滤/排序,再分页):
Skip(n):跳过前n条元素;Take(n):获取前n条元素。
// 跳过1条,取2条(分页:第2页,每页2条)
var pageList = list.Skip(1).Take(2);
// 先过滤年龄>=30,再升序,最后分页(跳过1条,取2条)
var filterPageList = list.Where(p => p.Age >=30).OrderBy(p => p.Age).Skip(1).Take(2);
7. 聚合函数:Max/Min/Average/Sum
对应SQL的聚合函数,用于计算数值类型的极值、平均值、总和,支持带Lambda表达式指定计算字段,可与Where等方法链式调用:
Max:最大值;Min:最小值;Average:平均值(返回double);Sum:总和。
list.Max(p => p.Age); // 最大年龄
list.Where(p => p.Age >=30).Max(p => p.Salary); // 年龄>=30的最高工资
int[] arr = { 1,2,3 };
arr.Min(); // 值类型集合可无参调用,返回1
8. 分组:GroupBy
- 作用:类似SQL的
group by,根据指定条件对集合分组; - 签名:
GroupBy(Func<TSource, TKey> keySelector),keySelector为分组条件; - 返回值:
IEnumerable<IGrouping<TKey, TSource>>,IGrouping包含Key属性(分组依据),且继承IEnumerable<T>,可对组内数据再调用Count/Max等方法; - 建议用
var简化返回值类型声明。
// 按年龄分组,计算每组人数、最高工资、平均工资
var groupList = list.GroupBy(p => p.Age);
foreach (var group in groupList)
{
Console.WriteLine($"分组年龄:{group.Key}");
Console.WriteLine($"组内人数:{group.Count()}");
Console.WriteLine($"组内最高工资:{group.Max(p => p.Salary)}");
foreach (var p in group) Console.WriteLine(p.Name);
}
9. 投影:Select
- 作用:将集合中元素逐项转换为其他类型/提取指定属性,类似SQL的
select子句; - 支持提取单个属性、拼接属性、转换类型,也可返回匿名类(需用
var接收); - 可与GroupBy/Where等方法链式调用,实现“过滤-分组-投影”一体化。
// 提取年龄属性
IEnumerable<int> ages = list.Select(p => p.Age);
// 提取姓名+年龄,拼接为字符串
IEnumerable<string> nameAge = list.Select(p => $"{p.Name},{p.Age}");
// 投影为匿名类,包含姓名、年龄、性别(bool转字符串)
var anonymousList = list.Select(p => new { p.Name, p.Age, Gender = p.Gender ? "男" : "女" });
// 分组后投影:年龄、组内人数、组内最高工资
var groupSelect = list.GroupBy(p => p.Age)
.Select(g => new { Age = g.Key, Count = g.Count(), MaxSal = g.Max(p => p.Salary) });
10. 链式调用
核心特性:Where/Select/OrderBy/GroupBy/Skip/Take等方法的返回值多为IEnumerable<T>(或其派生接口),因此可连续调用,实现多步骤数据处理,代码简洁且可读性高。
示例:获取Id>2的数据→按年龄分组→按分组年龄排序→取前2组→投影(年龄、人数、平均工资)
var result = list.Where(p => p.Id > 2)
.GroupBy(p => p.Age)
.OrderBy(g => g.Key)
.Take(2)
.Select(g => new { Age = g.Key, Count = g.Count(), AvgSal = g.Average(p => p.Salary) });
三、LINQ 的两种语法
1. 方法语法
- 即上述所有扩展方法的调用方式,结合Lambda表达式,最常用;
- 优势:编写复杂查询条件时更清晰,支持所有LINQ功能,链式调用灵活。
2. 查询语法
- 类似SQL的语法风格,以
from开头,select/group by结尾,关键字包括where/orderby/join等; - 底层:C#编译器会将查询语法编译为方法语法,运行时无区别;
- 优势:简单查询时代码更简洁,符合SQL开发者的使用习惯;
- 注意:所有查询语法均可改写为方法语法,反之亦然。
// 查询语法示例:过滤工资>3000→按年龄升序→投影匿名类(姓名、年龄、性别)
var result = from p in list
where p.Salary > 3000
orderby p.Age
select new { p.Name, p.Age, Gender = p.Gender ? "男" : "女" };
四、核心总结
- LINQ 是.NET Core 数据查询的核心技术,底层基于扩展方法和Lambda表达式,适用于所有实现
IEnumerable<T>的集合; - 委托是Lambda的基础,.NET 内置
Func/Action可替代自定义委托,简化代码; - 方法语法是LINQ的主流写法,支持链式调用,可实现过滤、排序、分页、分组、投影等所有操作;
- 关键方法需按场景选择:如存在性判断用
Any而非Count,获取单条数据根据“无/多匹配”选择Single/First系列; - 查询语法与方法语法等价,编译后无区别,简单查询用查询语法,复杂查询用方法语法。