Skip to content
Donghai's Blog
Go back

C# 技术备忘 - LINQ

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

  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班")}");

(完)

如果这篇文章刚好帮到了你,欢迎请我喝杯咖啡


分享这篇文章:

上一篇
使用DBeaver连接Dynamics 365 Dataverse
下一篇
为Astro站点添加Google Analytics
BlogsClub Meo Forever Blog