C# LINQ 所有方法详解 + 使用场景(标准查询运算符,分两类:查询语法/方法语法)
LINQ 分为延迟执行(惰性)和立即执行(求值)两大类,还有聚合、元素、生成、集合修改方法,全部附代码示例与适用场景。 底层基于IEnumerable<T>/IQueryable<T>(EF 数据库查询优先用 IQueryable,减少内存加载)。
一、筛选类(延迟执行,过滤数据)
1. Where 条件过滤
作用:按布尔条件筛选元素场景:数据过滤、多条件筛选、数据库条件查询
var list = new List<int> {1,2,3,4,5}; // 筛选大于3 var res = list.Where(x => x > 3); // 4,5 // 带索引重载:筛选偶数且索引大于1 var res2 = list.Where((x,idx) => x%2==0 && idx>1);EF 场景:db.Users.Where(u => u.Age > 18 && u.Status == 1)
2. OfType 按类型过滤
作用:集合中只取出指定类型元素,自动忽略不匹配类型场景:object 混合集合、控件集合、多态集合筛选
IList objects = new ArrayList { 1, "abc", 2, "def" }; var strs = objects.OfType<string>(); // "abc","def"二、投影/转换类(延迟执行,映射字段/对象)
1. Select 单对象投影映射
作用:转换每一个元素,支持字段提取、对象重组、计算场景:DTO 转换、只取需要字段、字段计算
var users = new List<User>(); // 只取姓名 var names = users.Select(u => u.Name); // 匿名类 var dto = users.Select(u => new {u.Id, u.Name, Age = u.Birth.Year}); // 带索引 var indexed = users.Select((u,i) => new {Index=i, u.Name});2. SelectMany 集合扁平化(一对多展开)
作用:把集合内嵌套集合展开,合并成一维序列场景:一对多关系、嵌套列表展开、子集合遍历
// 班级-学生 一对多 var classes = new List<Class>(); // 取出所有学生,扁平化 var allStudents = classes.SelectMany(c => c.Students); // 同时保留父对象 var classStudent = classes.SelectMany(c => c.Students, (cls,stu)=>new {cls.Name, stu.Name});三、排序类(延迟执行,排序)
1. OrderBy / OrderByDescending 单字段升序/降序
场景:单一条件排序
var asc = users.OrderBy(u => u.Age); // 年龄升序 var desc = users.OrderByDescending(u => u.CreateTime); // 创建时间倒序2. ThenBy / ThenByDescending 多字段次级排序
作用:主排序相同后,次要条件排序,必须跟在 OrderBy 后场景:多维度复合排序
// 先按年龄升序,同龄按分数降序 var sort = users.OrderBy(u=>u.Age).ThenByDescending(u=>u.Score);四、分区截取类(延迟执行,取部分数据)
1. Take(n) 取前 N 条
场景:分页首页、TopN 数据、限制返回条数
var top3 = list.Take(3); // 前3个2. TakeWhile 满足条件就持续取,遇到不满足立刻停止
场景:连续符合条件数据截取
var nums = new List<int>{1,2,4,1,5}; var take = nums.TakeWhile(x=>x<3); // 1,2(到4停止)3. Skip(n) 跳过前 N 条
场景:分页(Skip((page-1)*size).Take(size))
var page2 = list.Skip(2).Take(3); // 跳过2条,取3条4. SkipWhile 跳过满足条件元素,直到不满足开始取值
var skip = nums.SkipWhile(x=>x<3); // 4,1,5五、集合去重/对比(延迟执行)
1. Distinct 去重
场景:获取不重复数据、枚举唯一值
var arr = new int[]{1,1,2,2,3}; var unique = arr.Distinct(); // 1,2,3 // 自定义比较器(复杂对象去重) var userDistinct = users.Distinct(new UserNameComparer());2. Except 差集:A有B没有的元素
场景:对比两个集合,找出差异数据
var a = new List<int>{1,2,3}; var b = new List<int>{3,4,5}; var diff = a.Except(b); // 1,23. Intersect 交集:两者都存在的元素
场景:共同数据筛选、权限交集匹配
var same = a.Intersect(b); // 34. Union 并集,自动去重
场景:合并两个集合,去除重复
var all = a.Union(b); // 1,2,3,4,55. Concat 简单拼接,不去重
场景:单纯合并,允许重复
var concat = a.Concat(b); // 1,2,3,3,4,5六、元素获取方法(立即执行,返回单个元素,无匹配抛异常)
1. First() / FirstOrDefault()
- First():取第一个元素,空集合抛异常
- FirstOrDefault():无数据返回类型默认值(null/0) 场景:查询单条数据、第一条匹配记录
var first = users.Where(u=>u.Id==1).FirstOrDefault();2. Last() / LastOrDefault()
取序列最后一条,数据库 IQueryable 慎用(全查再取尾)
3. Single() / SingleOrDefault()
Single:只能有一条匹配,多/无都报错 SingleOrDefault:匹配0条返回默认,多条报错 场景:唯一主键查询(Id主键),确保只有一条数据
var user = db.Users.SingleOrDefault(u=>u.Id == 1001);4. ElementAt(index) / ElementAtOrDefault(index)
按索引取元素,越界抛异常 / 返回默认 场景:已知下标取值
七、聚合函数(立即执行,返回数值)
1. Count() 统计总数
无参数:全部数量;带条件Count(x=>x>0)统计符合条件数量 场景:分页总条数、条件计数
int total = users.Count(u => u.Age >= 18);2. LongCount() 返回 long 类型计数(超大集合)
3. Sum 求和
数字集合求和,支持投影Sum(x=>x.Score)
int totalScore = users.Sum(u => u.Score);4. Min / Max 最小/最大值
int maxAge = users.Max(u => u.Age); int minScore = users.Min(u => u.Score);5. Average 平均值
仅数值类型可用
double avgAge = users.Average(u => u.Age);6. Aggregate 自定义聚合(万能累加器)
场景:复杂合并、字符串拼接、自定义运算
var nums = new List<int>{1,2,3,4}; // 累加 1+2+3+4 int sum = nums.Aggregate((acc,cur)=>acc+cur); // 带初始值,拼接字符串 string str = nums.Aggregate("0", (s,x)=>s+"+"+x);八、判断存在性(立即执行,返回 bool)
1. Any() 是否存在至少一条数据
无参数:集合不为空;带条件:是否有满足条件元素推荐优先用 Any 判断存在,性能优于 Count>0
bool hasAdult = users.Any(u => u.Age >= 18);2. All() 全部元素都满足条件
场景:批量校验数据合规性
bool allPass = users.All(u => u.Score >= 60);3. Contains() 是否包含指定元素
基础类型直接传值;复杂对象需自定义比较器
var list = new List<int>{1,2,3}; bool exist = list.Contains(2);九、转换类型方法(立即执行,转为固定容器)
1. ToList() 转为 List
最常用,将 IEnumerable 加载到内存,断开延迟执行 场景:查询后多次遍历、修改集合、EF 查询落地内存
var list = db.Users.Where(u=>u.Age>18).ToList();2. ToArray() 转为 T[] 数组
3. ToDictionary(keySelector, valueSelector) 转字典
场景:主键快速索引,Id映射实体
var userDict = users.ToDictionary(k=>k.Id, v=>v.Name);4. ToLookup() 多键一对多字典(ILookup)
一个键对应多个值,分组专用 场景:按类别分组并快速索引
var ageGroup = users.ToLookup(u => u.Age); var age20 = ageGroup[20]; // 所有20岁用户5. AsEnumerable() / AsQueryable()
- AsQueryable():IEnumerable 转 IQueryable,支持LINQ表达式树
- AsEnumerable():强制切换为内存LINQ,数据库查询中局部处理 场景:EF中部分逻辑无法转SQL,强制内存计算
十、分组方法 GroupBy(延迟执行)
GroupBy 分组聚合
返回IGrouping<TKey,TElement>,支持多键、投影分组值 场景:报表分组、统计分类数据
// 单键分组:按年龄分组 var group = users.GroupBy(u => u.Age); foreach(var g in group) { int age = g.Key; int count = g.Count(); } // 多键分组(匿名类复合键) var multiGroup = users.GroupBy(u => new {u.Age, u.Gender}); // 分组同时映射元素 var groupName = users.GroupBy(u=>u.Age, u=>u.Name);十一、生成序列方法(无数据源,创建集合,延迟)
1. Range(start, count) 生成连续数字
var nums = Enumerable.Range(1,10); // 1~102. Repeat(value, count) 重复生成指定值
var fiveZero = Enumerable.Repeat(0,5);3. Empty() 返回空 IEnumerable
场景:方法返回IEnumerable,无数据时避免null
十二、zip 配对合并序列(延迟)
Zip 将两个序列按位置一一配对,长度取最短
场景:两个列表一一对应合并
var a = new List<int>{1,2,3}; var b = new List<string>{"a","b","c"}; var zip = a.Zip(b,(x,y)=>new{Num=x,Str=y});关键核心知识点
1. 延迟执行 vs 立即执行 区分
延迟(仅定义查询,遍历才执行)
Where/Select/SelectMany/OrderBy/Take/Skip/Distinct/GroupBy/Concat/Union/Except/Intersect/Zip/OfType 优点:多次拼接查询只会执行一次;EF 可拼接SQL优化
立即(调用时直接执行查询加载数据)
ToList/ToArray/ToDictionary/Count/Sum/Min/Max/Average/Aggregate/First/Any/All/Single/Contains 一旦调用,SQL/内存计算立刻执行,结果存入容器
2. IQueryable VS IEnumerable 使用场景
- 数据库查询(EF Core)优先 IQueryable:Where/Select/Take 会翻译为SQL,只拉取需要数据,性能高
- 复杂逻辑无法转SQL(本地方法、自定义函数):加
.AsEnumerable()切换内存LINQ - 内存集合(List/Array)只能用 IEnumerable
3. 高频业务场景速查表
| 需求 | 推荐LINQ方法 |
|---|---|
| 分页 | Skip + Take + Count |
| DTO转换 | Select 匿名类/实体 |
| 一对多展开 | SelectMany |
| 主键查单条 | SingleOrDefault |
| 判断数据存在 | Any() |
| 分组统计 | GroupBy + Count/Sum |
| 去重 | Distinct |
| 集合对比差异 | Except/Intersect/Union |
| 多条件排序 | OrderBy + ThenBy |
| 列表转字典索引 | ToDictionary |
| 内存落地查询结果 | ToList() |
4. 避坑提醒
- IQueryable 不要在循环内多次 ToList(),会重复查库
- First()/Single() 无数据抛异常,业务统一用 *OrDefault
- OrderBy 后必须 ThenBy,不能连续两个 OrderBy(后者覆盖前者)
- Count() > 0 性能远差于 Any(),判断存在一律 Any
- EF 中 Last() 会拉取全表再取最后一条,大数据禁用,改用 OrderByDescending + FirstOrDefault
