C# 技术备忘 - LINQ
2730 字
14 分钟
C# 技术备忘 - LINQ

准备示例数据源
本地创建控制台应用程序,添加数据源。
using System;using System.Collections.Generic;using System.Linq;
namespace DaikinITC.D365.Sample{ public class Program { static void Main(string[] args) { // [!code ++:12] // 准备一个学生列表作为数据源 List<Student> students = new List<Student> { new Student { Id = 1, Name = "张三", Age = 20, Class = "软件工程1班", Score = 85, Balance = 50.5m }, new Student { Id = 2, Name = "李四", Age = 22, Class = "软件工程2班", Score = 92, Balance = 120.0m }, new Student { Id = 3, Name = "王五", Age = 21, Class = "软件工程1班", Score = 78, Balance = null }, // 没开通 new Student { Id = 4, Name = "赵六", Age = 23, Class = "软件工程3班", Score = 88, Balance = 30.0m }, new Student { Id = 5, Name = "孙七", Age = 20, Class = "软件工程2班", Score = 95, Balance = 200.0m }, new Student { Id = 6, Name = "周八", Age = 22, Class = "软件工程1班", Score = 60, Balance = null }, // 没开通 }; Console.ReadKey(); } } // [!code ++:8] public class Student { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public string Class { get; set; } public int Score { get; set; } public decimal? Balance { get; set; } // 饭卡余额 }}#1 两种语法
- 查询表达式(类似SQL)
var result = from s in students where s.Age > 21 select s;- Lambda表达式 *掌握,项目经常使用
var result = students.Where(s => s.Age > 21);#2 查询
where
// 过滤(Where)—— 找出年龄大于21岁的学生var temp1 = students.Where(x => x.Age > 21);select
// 选择(Select)—— 只取出姓名和分数,生成新对象var temp2 = students.Select(s => new { s.Name, s.Score });selectMany
#3 排序
| 方法 | 排序方向 | 说明 |
|---|---|---|
OrderBy | 升序 | 对集合进行升序排序(从小到大) |
OrderByDescending | 降序 | 对集合进行降序排序(从大到小) |
ThenBy | 升序 | 在已有排序基础上,进行后续升序排序 |
ThenByDescending | 降序 | 在已有排序基础上,进行后续降序排序 |
// 按分数升序var sorted2 = students.OrderBy(x => x.Score);
// 按分数降序var sorted1 = students.OrderByDescending(x => x.Score);
// 先按班级升序,再按分数降序var multiSort = students.OrderBy(x => x.Class).ThenByDescending(x => x.Score);#4 聚合方法 (Count, Sum, Average, Max, Min)
var stdCount = students.Count();Console.WriteLine($"一共有 {stdCount} 位学生");
var totalBalance = students.Sum(x => x.Balance);Console.WriteLine($"这些学生的饭卡余额一共是 {totalBalance}");
var avgScore = students.Average(s => s.Score);var maxScore = students.Max(s => s.Score);var minScore = students.Min(s => s.Score);Console.WriteLine($"平均分: {avgScore}, 最高分: {maxScore}, 最低分: {minScore}");#5 条件判断
| 方法 | 返回类型 | 说明 | 使用场景 |
|---|---|---|---|
All | bool | 判断所有元素是否都满足指定条件 | 检查全员达标、 |
Any | bool | 判断是否存在元素满足指定条件 | (或集合是否为空) |
Contains | bool | 判断集合中是否包含指定的特定元素 | 检查某个对象是否在集合中 |
All
// 1. 所有学生都及格了吗?(>= 60分)bool allPassed = students.All(s => s.Score >= 60);Console.WriteLine($"所有学生都及格: {allPassed}");
// 2. 所有学生都超过80分了吗?bool allOver80 = students.All(s => s.Score >= 80);
// 3. 所有学生都满18岁了吗?bool allAdult = students.All(s => s.Age >= 18);Console.WriteLine($"所有学生都成年: {allAdult}");Any
// 1. 判断集合是否有元素(比 Count() > 0 高效)bool hasStudents = students.Any();Console.WriteLine($"集合有学生吗: {hasStudents}");
// 2. 判断是否有学生超过90分bool hasHighScore = students.Any(s => s.Score > 90);Console.WriteLine($"有超过90分的学生吗: {hasHighScore}");
// 3. 判断是否有不及格的学生bool hasFailed = students.Any(s => s.Score < 60);Console.WriteLine($"有不及格的学生吗: {hasFailed}");Contains
// 1. 基本类型 Containsvar ids = new List<int> { 1, 2, 3, 4, 5 };Console.WriteLine($"包含 3: {ids.Contains(3)}"); // TrueConsole.WriteLine($"包含 10: {ids.Contains(10)}"); // False
var names = new List<string> { "张三", "李四", "王五" };Console.WriteLine($"包含 '张三': {names.Contains("张三")}"); // TrueConsole.WriteLine($"包含 '赵六': {names.Contains("赵六")}"); // False
// 2. 引用类型 Contains(需要实现 IEquatable 或重写 Equals)var targetStudent = students.First(s => s.Id == 2);bool containsTarget = students.Contains(targetStudent);Console.WriteLine($"包含李四吗: {containsTarget}"); // True
// 3. Contains 用于字符串列表var classNames = new List<string> { "软件工程1班", "软件工程2班", "软件工程3班" };bool hasClass = classNames.Contains("软件工程1班");Console.WriteLine($"有软件工程1班吗: {hasClass}"); // True#6 分组和连接(Join)
(2026-08-24 14:52<28>28> 补充)
| 方法 | 类型 | 说明 |
|---|---|---|
GroupBy | 分组 | 根据指定的键对集合元素进行分组 |
Join | 连接(内连接) | 基于匹配键关联两个集合,类似 SQL INNER JOIN |
GroupJoin | 连接(分组连接) | 关联两个集合并将结果分组,类似 SQL LEFT JOIN |
GroupBy
根据指定的键对集合元素进行分组
1. 基础分组:按班级分组
var groupsByClass = students.GroupBy(s => s.Class);foreach (var group in groupsByClass){ Console.WriteLine($"班级: {group.Key}"); foreach (var student in group) { Console.WriteLine($" {student.Name} - {student.Score}分"); }}2.分组 + 聚合统计
var classStats = students.GroupBy(s => s.Class) .Select(g => new { ClassName = g.Key, Count = g.Count(), AvgScore = g.Average(s => s.Score), MaxScore = g.Max(s => s.Score), MinScore = g.Min(s => s.Score) });foreach (var stat in classStats){ Console.WriteLine($"{stat.ClassName}: {stat.Count}人, 平均分{stat.AvgScore:F1}, " + $"最高{stat.MaxScore}, 最低{stat.MinScore}");}3.分组 + 筛选
// (只显示平均分大于80的班级)var highClass = students.GroupBy(s => s.Class) .Where(g => g.Average(s => s.Score) > 80) .Select(g => new { g.Key, AvgScore = g.Average(s => s.Score) });foreach (var hc in highClass){ Console.WriteLine($"{hc.Key}: 平均分{hc.AvgScore:F1}");}4.多键分组(按班级和年龄分组)
var multiGroup = students.GroupBy(s => new { s.Class, s.Age });foreach (var group in multiGroup){ Console.WriteLine($"班级: {group.Key.Class}, " + $"年龄: {group.Key.Age}, 人数: {group.Count()}");}Join
基于匹配键关联两个集合,类似 SQL INNER JOIN。
调整关联数据:
var classes = new List<Class>{ new Class { Id = 1, ClassName = "软件工程1班", Teacher = "王老师" }, new Class { Id = 2, ClassName = "软件工程2班", Teacher = "李老师" }, new Class { Id = 3, ClassName = "软件工程3班", Teacher = "张老师" },};
public class Class{ public int Id { get; set; } public string ClassName { get; set; } public string Teacher { get; set; }}
// 修改 Student 类,添加 ClassIdpublic class Student{ public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public int ClassId { get; set; } // 外键 public string Class { get; set; } // 保留原字段 public int Score { get; set; } public decimal? Balance { get; set; }}Join - 内连接(查询语法)
var querySyntax = from s in students join c in classes on s.ClassId equals c.Id select new { s.Name, s.Score, c.ClassName, c.Teacher };foreach (var item in querySyntax){ Console.WriteLine($"{item.Name} - {item.ClassName} - 班主任: {item.Teacher}"); //张三 - 软件工程1班 - 班主任: 王老师 //李四 - 软件工程2班 - 班主任: 李老师 //王五 - 软件工程1班 - 班主任: 王老师 //赵六 - 软件工程3班 - 班主任: 张老师 //孙七 - 软件工程2班 - 班主任: 李老师 //周八 - 软件工程1班 - 班主任: 王老师}Join - 内连接
var methodSyntax = students.Join(classes, s => s.ClassId, // 左表外键 c => c.Id, // 右表主键 (s, c) => new { // 结果投影 s.Name, s.Score, c.ClassName, c.Teacher });foreach (var item in methodSyntax){ Console.WriteLine($"{item.Name} - {item.ClassName} - {item.Score}分"); //张三 - 软件工程1班 - 85分 //李四 - 软件工程2班 - 92分 //王五 - 软件工程1班 - 78分 //赵六 - 软件工程3班 - 88分 //孙七 - 软件工程2班 - 95分 //周八 - 软件工程1班 - 60分}Join + 条件过滤
var filteredJoin = students.Join(classes, s => s.ClassId, c => c.Id, (s, c) => new { s, c }) .Where(x => x.s.Score > 80) .Select(x => new { x.s.Name, x.s.Score, x.c.ClassName, x.c.Teacher });GroupJoin(分组连接)
GroupJoin - 基本用法(查询语法)
var groupQuery = from c in classes join s in students on c.Id equals s.ClassId into studentGroup select new { ClassName = c.ClassName, Teacher = c.Teacher, Students = studentGroup, StudentCount = studentGroup.Count() };
foreach (var item in groupQuery){ Console.WriteLine($"班级: {item.ClassName} ({item.Teacher}) - " + $"学生数: {item.StudentCount}"); foreach (var student in item.Students) { Console.WriteLine($" {student.Name} - {student.Score}分"); }}GroupJoin - 方法语法
var groupMethod = classes.GroupJoin( students, c => c.Id, // 左表主键 s => s.ClassId, // 右表外键 (c, studentGroup) => new { ClassName = c.ClassName, Teacher = c.Teacher, Students = studentGroup, AvgScore = studentGroup.Average(s => s.Score) });foreach (var item in groupMethod){ Console.WriteLine($"{item.ClassName} - 平均分: {item.AvgScore:F1}");}GroupJoin + 处理空组
var groupWithEmpty = classes.GroupJoin( students, c => c.Id, s => s.ClassId, (c, studentGroup) => new { ClassName = c.ClassName, Students = studentGroup.DefaultIfEmpty(), // 无学生时返回默认值 HasStudents = studentGroup.Any() });foreach (var item in groupWithEmpty){ Console.WriteLine($"{item.ClassName}: {(item.HasStudents ? "有学生" : "无学生")}");}#7 集合转换
(2026-08-24 16:12<06>06> 补充)
这些方法把查询结果立即执行并转换成不同数据类型。
| 方法 | 描述 |
|---|---|
ToList | 将实现了IEnumerable<T>接口的集合转换为一个List<T>类型的对象,属于将集合转换为特定类型列表的方法 |
ToArray | 将一个实现了IEnumerable<T>接口的集合转换为一个数组,属于将集合转换为数组类型的方法 |
ToDictionary | 将一个IEnumerable<T>集合转换为一个Dictionary<TKey,TValue>键值对集合(字典)的方法,注意 ToDictionary 要求键唯一,否则抛出异常 |
ToLookup | 将一个IEnumerable<T>集合转换为一个泛型Lookup<TKey,TElement>,Lookup<TKey,TElement>一个一对多字典,用于将键映射到值的集合 |
ToList() - 转成 List
var students = new List<Student>{ new Student { Id = 1, Name = "张三", Age = 20, Score = 85, Balance = 50.5m }, new Student { Id = 2, Name = "李四", Age = 22, Score = 92, Balance = 120.0m }, new Student { Id = 3, Name = "王五", Age = 21, Score = 78, Balance = null },};
// ToList - 立即执行,生成新的 Listvar highScoreList = students.Where(s => s.Score >= 80).ToList();Console.WriteLine($"高分学生数: {highScoreList.Count}"); // 输出:2
// 关键:ToList 会立即执行并固化结果var filtered = students.Where(s => s.Age > 20);students.Add(new Student { Id = 4, Name = "赵六", Age = 25, Score = 88 }); // 后添加// 延迟执行:filtered 会包含赵六Console.WriteLine($"延迟执行数量: {filtered.Count()}"); // 输出:3(李四、王五、赵六)
var filteredToList = students.Where(s => s.Age > 20).ToList();students.Add(new Student { Id = 5, Name = "孙七", Age = 24, Score = 90 }); // 再添加// ToList 已固化:不包含孙七Console.WriteLine($"ToList固化数量: {filteredToList.Count}"); // 输出:3(李四、王五、赵六)ToArray() - 转成数组
// ToArray - 转成数组var highScoreArray = students.Where(s => s.Score >= 80).ToArray();Console.WriteLine($"数组类型: {highScoreArray.GetType()}"); // 输出:Student[]Console.WriteLine($"数组长度: {highScoreArray.Length}"); // 输出:2
// 使用场景:需要数组的索引访问for (int i = 0; i < highScoreArray.Length; i++){ Console.WriteLine($"第{i}个高分学生: {highScoreArray[i].Name}");}ToDictionary() - 转成字典(常用)
// ToDictionary - 把集合转成字典,必须指定唯一的 Keytry{ // 用 Id 作为 Key(唯一) var dictById = students.ToDictionary(s => s.Id); Console.WriteLine($"字典数量: {dictById.Count}");
// 通过 Key 快速查找(O(1) 时间复杂度) var student = dictById[2]; Console.WriteLine($"ID=2的学生: {student.Name}"); // 输出:李四
// 遍历字典 foreach (var kvp in dictById) { Console.WriteLine($"Key: {kvp.Key}, Name: {kvp.Value.Name}"); }}catch (ArgumentException ex){ Console.WriteLine($"转字典报错: {ex.Message}"); // 如果 Key 重复会报错}
// 带值选择器的 ToDictionary(可以只取部分字段)var dictNameAndScore = students.ToDictionary( s => s.Id, // Key 选择器 s => new { s.Name, s.Score } // Value 选择器);Console.WriteLine($"学生2: {dictNameAndScore[2].Name} - {dictNameAndScore[2].Score}分");ToLookup() - 一对多字典(类似分组)
// ToLookup - 一个 Key 对应多个值(类似分组,但可以索引)var lookupByClass = students.ToLookup(s => s.Class);
// 获取某个班级的所有学生var class1Students = lookupByClass["软件工程1班"];Console.WriteLine($"软件工程1班学生数: {class1Students.Count()}");foreach (var s in class1Students){ Console.WriteLine($" {s.Name}");}
// 检查某个 Key 是否存在Console.WriteLine($"是否有软件工程1班: {lookupByClass.Contains("软件工程1班")}");Console.WriteLine($"是否有软件工程5班: {lookupByClass.Contains("软件工程5班")}");支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!
相关文章智能推荐
1
C# 技术备忘 (1)
Csharp开发备忘笔记,记录日常开发中遇到的小技巧、代码片段和最佳实践
2
使用DBeaver连接Dynamics 365 Dataverse
Dynamics 365在数据分析或开发工作中,有时我们希望能用熟悉的SQL工具直接查询Dataverse中的数据,除了使用SSMS、SQL 4 CDS外,还可以使用DBeaver,下文详细记录了配置过程。
3
Dynamics 365 事件框架、事件执行管道
Dynamics 365Dynamics 365 插件运行的“流水线”到底长什么样?
4
了解2FA(双因素认证)
理解计算机2FA,即双因素认证,是在登录或操作账户时,除了密码之外,还需要提供第二种身份验证方式,以增强账户安全性。
5
初尝Github Actions
Git和GithubGitHub Actions 是 GitHub 提供的 CI/CD 服务,早在 2018 年 10 月就已推出,但我一直未曾使用。最近遇到一个需求(如何更优雅地部署博客),才终于想起了它。目前我的站点放在自己的服务器上,每次都要先在本地编译,再手动打包上传到服务器,接着解压到 public 文件夹,整个过程有点繁琐。而博客的源码已经托管在 GitHub 上,我想,如果每次 push 后都能自动完成部署,那就太好了。于是研究了下 Github Actions,顺便把一些内容记录下来,方便日后查阅。
随机文章随机推荐
