LINQ语言集成查询是一系列直接将查询功能集成到 C# 语言的技术统称,LINQ主要操作集合(IEnumerable<T>)。
Table of contents
Open Table of contents
准备示例数据源
本地创建控制台应用程序,添加数据源。
using System;
using System.Collections.Generic;
using System.Linq;
namespace DaikinITC.D365.Sample
{
public class Program
{
static void Main(string[] args)
{
// 准备一个学生列表作为数据源
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();
}
}
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; } // 饭卡余额
}
}Program.cs
#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. 基本类型 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班")}"); (完)
如果这篇文章刚好帮到了你,欢迎请我喝杯咖啡!