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 两种语法#

  1. 查询表达式(类似SQL)
var result = from s in students
where s.Age > 21
select s;
  1. 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 条件判断#

方法返回类型说明使用场景
Allbool判断所有元素是否都满足指定条件检查全员达标、
Anybool判断是否存在元素满足指定条件(或集合是否为空)
Containsbool判断集合中是否包含指定的特定元素检查某个对象是否在集合中

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. 基本类型 Contains
var ids = new List<int> { 1, 2, 3, 4, 5 };
Console.WriteLine($"包含 3: {ids.Contains(3)}"); // True
Console.WriteLine($"包含 10: {ids.Contains(10)}"); // False
var names = new List<string> { "张三", "李四", "王五" };
Console.WriteLine($"包含 '张三': {names.Contains("张三")}"); // True
Console.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> 补充)

方法类型说明
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 类,添加 ClassId
public 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> 补充)

这些方法把查询结果立即执行并转换成不同数据类型。

方法描述
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 - 立即执行,生成新的 List
var 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 - 把集合转成字典,必须指定唯一的 Key
try
{
// 用 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班")}");

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
C# 技术备忘 - LINQ
https://mgrowup.com/posts/csharp/memp-02/
作者
Donghai
发布于
2026-08-24
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Donghai
Hello, I'm Donghai.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
62
分类
9
标签
37
总字数
76,949
运行时长
0
最后活动
0 天前
站点信息
构建平台
Vercel
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0