LINQ 核心知识点整理

1 阅读4分钟

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)简化规则
  1. 可省略参数类型,编译器根据委托自动推断;
  2. 方法体仅一行代码+有返回值:省略花括号{}return
  3. 方法体仅一行代码+无返回值(Action委托):省略花括号{}
  4. 一个参数:省略参数的圆括号()
(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 的底层实现依赖扩展方法,用于向现有类型动态添加新方法,无需修改原类型代码,定义规则:

  1. 扩展方法所在类必须是static静态类;
  2. 扩展方法本身必须是public + static
  3. 第一个参数必须带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 ? "男" : "女" };

四、核心总结

  1. LINQ 是.NET Core 数据查询的核心技术,底层基于扩展方法Lambda表达式,适用于所有实现IEnumerable<T>的集合;
  2. 委托是Lambda的基础,.NET 内置Func/Action可替代自定义委托,简化代码;
  3. 方法语法是LINQ的主流写法,支持链式调用,可实现过滤、排序、分页、分组、投影等所有操作;
  4. 关键方法需按场景选择:如存在性判断用Any而非Count,获取单条数据根据“无/多匹配”选择Single/First系列;
  5. 查询语法与方法语法等价,编译后无区别,简单查询用查询语法,复杂查询用方法语法。