目 录CONTENT

文章目录

CSharp(三十九) LINQ 详解 —— 定义与使用

C# LINQ 详解 —— 定义与使用


一、什么是 LINQ?

LINQ 全称 Language Integrated Query(语言集成查询),是 C# 提供的一套像写 SQL 一样操作数据集合的工具。

打个简单的比喻:

不用 LINQ 时,你要从 100 个学生中找出分数大于 80 的人,需要写 for 循环 + if 判断 + 建临时列表。用 LINQ 后,一行代码搞定:students.Where(s => s.Score > 80)

核心思想:把"查询"这件事变成 C# 语言的一部分,操作数组、集合、数据库就像写 SQL 一样直观。


二、为什么需要 LINQ?

2.1 不用 LINQ —— 传统写法有多痛苦

// 需求:从学生列表中找到所有及格的,按分数从高到低排序,只取前 3 名

// ❌ 没有 LINQ 的写法
List<Student> passed = new List<Student>();
foreach (Student s in students)
{
    if (s.Score >= 60)
    {
        passed.Add(s);
    }
}
// 手动排序(冒泡排序……)
for (int i = 0; i < passed.Count - 1; i++)
{
    for (int j = 0; j < passed.Count - 1 - i; j++)
    {
        if (passed[j].Score < passed[j + 1].Score)
        {
            var temp = passed[j];
            passed[j] = passed[j + 1];
            passed[j + 1] = temp;
        }
    }
}
// 取前 3
List<Student> top3 = new List<Student>();
for (int i = 0; i < 3 && i < passed.Count; i++)
{
    top3.Add(passed[i]);
}

2.2 用 LINQ —— 一行链式调用

// ✅ 用 LINQ
var top3 = students
    .Where(s => s.Score >= 60)
    .OrderByDescending(s => s.Score)
    .Take(3);

同样的需求,传统写法 20+ 行,LINQ 只要 4 行。代码读起来就是一句话:筛选 → 排序 → 取前 3。


三、准备测试数据

下面所有例子都基于这段数据:

using System;
using System.Collections.Generic;
using System.Linq;

public class Student
{
    public string Name;
    public int Age;
    public int Score;
    public string City;
    public string Subject;

    public override string ToString()
    {
        return $"{Name} | {Age}岁 | {City} | {Subject} | {Score}分";
    }
}

// 准备测试数据
List<Student> students = new List<Student>
{
    new Student { Name = "张三", Age = 18, Score = 92, City = "北京", Subject = "数学" },
    new Student { Name = "李四", Age = 19, Score = 85, City = "上海", Subject = "数学" },
    new Student { Name = "王五", Age = 18, Score = 76, City = "北京", Subject = "语文" },
    new Student { Name = "赵六", Age = 20, Score = 58, City = "广州", Subject = "数学" },
    new Student { Name = "孙七", Age = 19, Score = 88, City = "上海", Subject = "语文" },
    new Student { Name = "周八", Age = 20, Score = 95, City = "北京", Subject = "英语" },
    new Student { Name = "吴九", Age = 18, Score = 45, City = "广州", Subject = "英语" },
    new Student { Name = "郑十", Age = 19, Score = 72, City = "深圳", Subject = "数学" },
};

四、LINQ 的两种写法

4.1 方法语法(Method Syntax)—— 推荐,最常用

var result = students
    .Where(s => s.Score >= 60)
    .OrderByDescending(s => s.Score);

像搭积木一样,一个方法接一个方法,链式调用。

4.2 查询语法(Query Syntax)—— 像 SQL

var result = from s in students
             where s.Score >= 60
             orderby s.Score descending
             select s;

建议:初学者先学方法语法(直观、链式、IDE 提示好),查询语法熟悉一下能看懂就行。本文后续全部用方法语法讲解。


五、LINQ 核心操作符分类详解

5.1 筛选操作 —— Where、OfType

Where —— 按条件过滤

// 找出所有及格的
var passed = students.Where(s => s.Score >= 60);

// 找出北京的学生
var fromBeijing = students.Where(s => s.City == "北京");

// 多条件:北京的及格学生
var result = students.Where(s => s.City == "北京" && s.Score >= 60);

// 带索引的 Where(i 是位置)
var withIndex = students.Where((s, i) => s.Score >= 80 && i < 5);

输出及格学生:

foreach (var s in passed)
    Console.WriteLine(s);
张三 | 18岁 | 北京 | 数学 | 92分
李四 | 19岁 | 上海 | 数学 | 85分
王五 | 18岁 | 北京 | 语文 | 76分
孙七 | 19岁 | 上海 | 语文 | 88分
周八 | 20岁 | 北京 | 英语 | 95分
郑十 | 19岁 | 深圳 | 数学 | 72分

OfType —— 按类型筛选

// 从混杂类型中筛选出特定类型
object[] items = { "hello", 123, "world", 456, "!" };
var strings = items.OfType<string>();

foreach (var s in strings)
    Console.WriteLine(s);
// 输出: hello world !

5.2 投影操作 —— Select、SelectMany

Select —— 把每个元素转换成新形式

// 只取姓名,变成 string 列表
var names = students.Select(s => s.Name);
// 结果: ["张三", "李四", "王五", "赵六", "孙七", "周八", "吴九", "郑十"]

// 转换成新对象(匿名类型)
var summaries = students.Select(s => new
{
    s.Name,
    s.Score,
    IsPassed = s.Score >= 60,
    Description = $"{s.Name}考了{s.Score}分"
});

foreach (var item in summaries)
    Console.WriteLine(item.Description);
张三考了92分
李四考了85分
王五考了76分
...

Select 带索引

// 给每个学生加上排名(索引+1)
var ranked = students.Select((s, i) => new { Rank = i + 1, s.Name, s.Score });

foreach (var item in ranked)
    Console.WriteLine($"第{item.Rank}名: {item.Name} {item.Score}分");

SelectMany —— 把嵌套集合"拍平"

// 每个学生有一组成绩,想得到所有成绩的扁平列表
var classGroups = new List<List<Student>>
{
    new List<Student> { /* 一班学生 */ },
    new List<Student> { /* 二班学生 */ },
    new List<Student> { /* 三班学生 */ },
};

var allStudents = classGroups.SelectMany(g => g);  // 所有班的全部学生

生活中的例子:

string[] sentences = { "Hello World", "LINQ is great", "CSharp rocks" };

// 把每个句子按空格拆分,然后拍平成所有单词
var allWords = sentences.SelectMany(s => s.Split(' '));

foreach (var word in allWords)
    Console.WriteLine(word);
Hello
World
LINQ
is
great
CSharp
rocks

5.3 排序操作 —— OrderBy、OrderByDescending、ThenBy

// 按分数升序(从小到大)
var byScore = students.OrderBy(s => s.Score);

// 按分数降序(从大到小)
var byScoreDesc = students.OrderByDescending(s => s.Score);

// 多级排序:先按年龄,再按分数降序
var multiSort = students
    .OrderBy(s => s.Age)           // 第一级:按年龄升序
    .ThenByDescending(s => s.Score); // 第二级:同年龄内按分数降序

// 年龄升序,分数也升序
var multiSort2 = students
    .OrderBy(s => s.Age)
    .ThenBy(s => s.Score);

输出多级排序结果:

foreach (var s in multiSort)
    Console.WriteLine(s);
吴九 | 18岁 | 广州 | 英语 | 45分
王五 | 18岁 | 北京 | 语文 | 76分
张三 | 18岁 | 北京 | 数学 | 92分
郑十 | 19岁 | 深圳 | 数学 | 72分
李四 | 19岁 | 上海 | 数学 | 85分
孙七 | 19岁 | 上海 | 语文 | 88分
赵六 | 20岁 | 广州 | 数学 | 58分
周八 | 20岁 | 北京 | 英语 | 95分

Reverse —— 反转

var reversed = students.OrderBy(s => s.Score).Reverse();  // 变成降序

5.4 分组操作 —— GroupBy

// 按城市分组
var byCity = students.GroupBy(s => s.City);

foreach (var group in byCity)
{
    Console.WriteLine($"--- {group.Key} ({group.Count()}人) ---");
    foreach (var s in group)
    {
        Console.WriteLine($"  {s.Name} - {s.Score}分");
    }
}

输出:

--- 北京 (3人) ---
  张三 - 92分
  王五 - 76分
  周八 - 95分
--- 上海 (2人) ---
  李四 - 85分
  孙七 - 88分
--- 广州 (2人) ---
  赵六 - 58分
  吴九 - 45分
--- 深圳 (1人) ---
  郑十 - 72分

分组后投影:

// 分组后统计每个城市的平均分
var cityAvg = students.GroupBy(s => s.City)
    .Select(g => new
    {
        City = g.Key,
        Count = g.Count(),
        AvgScore = g.Average(s => s.Score),
        MaxScore = g.Max(s => s.Score)
    });

foreach (var c in cityAvg)
{
    Console.WriteLine($"{c.City}: {c.Count}人, 均分{c.AvgScore:F1}, 最高{c.MaxScore}");
}

输出:

北京: 3人, 均分87.7, 最高95
上海: 2人, 均分86.5, 最高88
广州: 2人, 均分51.5, 最高58
深圳: 1人, 均分72.0, 最高72

5.5 集合操作 —— 并集、交集、差集、去重

int[] a = { 1, 2, 3, 4, 5 };
int[] b = { 4, 5, 6, 7, 8 };

// 并集(去重)
var union = a.Union(b);         // 1, 2, 3, 4, 5, 6, 7, 8

// 交集
var intersect = a.Intersect(b);  // 4, 5

// 差集(a 中有 b 中没有)
var except = a.Except(b);        // 1, 2, 3

// 拼接(保留重复)
var concat = a.Concat(b);        // 1, 2, 3, 4, 5, 4, 5, 6, 7, 8

学生场景示例:

var mathStudents = students.Where(s => s.Subject == "数学");
var beijingStudents = students.Where(s => s.City == "北京");

// 交集:既学数学又是北京的
var both = mathStudents.Intersect(beijingStudents);  // 张三

// 去重(按城市)
var distinctCities = students.Select(s => s.City).Distinct();
// 北京, 上海, 广州, 深圳

5.6 量词操作 —— Any、All、Contains

// Any:是否存在满足条件的元素?
bool hasFailed = students.Any(s => s.Score < 60);    // True(有不及格的)

// All:所有元素都满足条件?
bool allPassed = students.All(s => s.Score >= 60);   // False(不是所有人都及格)

// Contains:集合中是否包含某个元素?
int[] nums = { 1, 2, 3, 4, 5 };
bool has3 = nums.Contains(3);  // True

Any 和 All 的细节:

// Any() 不带参数——判断集合是否非空
List<int> empty = new List<int>();
Console.WriteLine(empty.Any());  // False

List<int> hasData = new List<int> { 1, 2, 3 };
Console.WriteLine(hasData.Any());  // True


// All 对空集合返回 True(逻辑上的"空真")
List<int> emptyList = new List<int>();
Console.WriteLine(emptyList.All(x => x > 0));  // True ← 注意!

5.7 聚合操作 —— Sum、Average、Count、Max、Min、Aggregate

// 总和
int totalScore = students.Sum(s => s.Score);          // 611

// 平均
double avgScore = students.Average(s => s.Score);     // 76.375

// 计数
int count = students.Count();                          // 8
int passedCount = students.Count(s => s.Score >= 60);  // 6

// 最大 / 最小
int maxScore = students.Max(s => s.Score);             // 95
int minScore = students.Min(s => s.Score);             // 45

// LongCount —— 用于超大集合
long bigCount = students.LongCount();

Aggregate —— 自定义累加(最灵活)

// 用 Aggregate 实现连乘:1 * 2 * 3 * 4 * 5
int[] nums = { 1, 2, 3, 4, 5 };
int product = nums.Aggregate((current, next) => current * next);
Console.WriteLine(product);  // 120

// 带初始值的 Aggregate:用逗号拼接所有名字
string allNames = students
    .Select(s => s.Name)
    .Aggregate("", (current, next) => current == "" ? next : current + ", " + next);

Console.WriteLine(allNames);
// 输出: 张三, 李四, 王五, 赵六, 孙七, 周八, 吴九, 郑十

Aggregate 执行过程图解:

初始: ""
第一步: "" + "张三"  → "张三"
第二步: "张三" + ", 李四" → "张三, 李四"
第三步: "张三, 李四" + ", 王五" → "张三, 李四, 王五"
...依此类推

5.8 分区操作 —— Take、Skip、TakeWhile、SkipWhile

// Take(n):取前 n 个
var top3 = students.OrderByDescending(s => s.Score).Take(3);

// Skip(n):跳过前 n 个
var skip2 = students.OrderByDescending(s => s.Score).Skip(2);

// Take + Skip 组合 = 分页!
int pageSize = 3;
int pageNum = 2;  // 第 2 页
var page = students
    .OrderBy(s => s.Name)
    .Skip((pageNum - 1) * pageSize)  // 跳过前 3 个
    .Take(pageSize);                 // 取 3 个
// 结果: 第 4、5、6 个学生(按姓名排序)

// TakeWhile:从开头取,直到条件不满足
int[] numbers = { 2, 4, 6, 7, 8, 10 };
var takeWhile = numbers.TakeWhile(n => n % 2 == 0);  // 2, 4, 6(遇到 7 停止)

// SkipWhile:从开头跳,直到条件不满足
var skipWhile = numbers.SkipWhile(n => n % 2 == 0);  // 7, 8, 10

5.9 元素操作 —— First、Last、Single、ElementAt

基本查询——按是否抛异常分类:

方法 找不到时 存在多个时
First 抛异常 返回第一个
FirstOrDefault 返回默认值 返回第一个
Last 抛异常 返回最后一个
LastOrDefault 返回默认值 返回最后一个
Single 抛异常 抛异常
SingleOrDefault 返回默认值 抛异常
ElementAt(i) 抛异常 返回第 i 个
ElementAtOrDefault(i) 返回默认值 返回第 i 个
// First —— 取第一个
var first = students.First(s => s.Score >= 60);         // 张三
var firstOrDefault = students.FirstOrDefault(s => s.Score > 100);  // null

// Single —— 确保只有一个
// students.Single(s => s.Score > 90);  // ❌ 有张三和周八两个,抛异常!
// students.Single(s => s.Score == 100); // ❌ 没有,抛异常!
var single = students.Single(s => s.Name == "张三");    // ✅ 只有一个是张三

// ElementAt —— 按索引获取
var third = students.Where(s => s.Score >= 60).ElementAt(2);  // 第三个及格的

First 和 Single 的区别很重要:

  • First:我只要一个,有没有多个无所谓
  • Single:我必须确保只有一个,多了少了都不行

5.10 连接操作 —— Join、GroupJoin

Join —— 内连接(两张表匹配)

// 学生表
var students = new List<Student> { /* ... 上面定义的学生 */ };

// 城市评级表
var cityRatings = new List<CityRating>
{
    new CityRating { City = "北京", Rating = "一线" },
    new CityRating { City = "上海", Rating = "一线" },
    new CityRating { City = "广州", Rating = "一线" },
    new CityRating { City = "深圳", Rating = "一线" },
    new CityRating { City = "杭州", Rating = "新一线" },
};

public class CityRating
{
    public string City;
    public string Rating;
}

// Join:学生和城市评级关联
var joined = students.Join(
    cityRatings,              // 第二张表
    s => s.City,              // 学生表的关联键
    c => c.City,              // 城市表的关联键
    (s, c) => new             // 结果投影
    {
        s.Name,
        s.City,
        c.Rating,
        s.Score
    }
);

foreach (var item in joined)
    Console.WriteLine($"{item.Name} | {item.City} | {item.Rating} | {item.Score}分");

输出:

张三 | 北京 | 一线 | 92分
李四 | 上海 | 一线 | 85分
王五 | 北京 | 一线 | 76分
赵六 | 广州 | 一线 | 58分
孙七 | 上海 | 一线 | 88分
周八 | 北京 | 一线 | 95分
吴九 | 广州 | 一线 | 45分
郑十 | 深圳 | 一线 | 72分

注意:杭州在城市评级表中但没有学生,所以不出现。这是内连接(INNER JOIN)。

GroupJoin —— 分组连接(LEFT JOIN 效果)

var groupJoined = cityRatings.GroupJoin(
    students,
    c => c.City,
    s => s.City,
    (c, studentGroup) => new
    {
        c.City,
        c.Rating,
        Students = studentGroup,
        Count = studentGroup.Count()
    }
);

foreach (var item in groupJoined)
{
    Console.Write($"{item.City}({item.Rating}): {item.Count}人");
    if (item.Count > 0)
    {
        double avg = item.Students.Average(s => s.Score);
        Console.Write($", 均分{avg:F1}");
    }
    Console.WriteLine();
}

输出:

北京(一线): 3人, 均分87.7
上海(一线): 2人, 均分86.5
广州(一线): 2人, 均分51.5
深圳(一线): 1人, 均分72.0
杭州(新一线): 0人

5.11 生成操作 —— Range、Repeat、Empty

// Range:生成连续整数序列
var numbers = Enumerable.Range(1, 10); // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

// Repeat:重复生成
var stars = Enumerable.Repeat("*", 5); // "*", "*", "*", "*", "*"

// Empty:空集合
var empty = Enumerable.Empty<int>();   // 空的 int 集合

实用例子:快速生成 1+2+...+100

int sum = Enumerable.Range(1, 100).Sum();
Console.WriteLine(sum);  // 5050

5.12 转换操作 —— ToList、ToArray、ToDictionary、ToLookup

// 转为 List
List<Student> list = students.Where(s => s.Score >= 60).ToList();

// 转为数组
Student[] array = students.Where(s => s.Score >= 60).ToArray();

// 转为 Dictionary(key 必须唯一!)
Dictionary<string, Student> dict = students
    .Where(s => s.City == "北京")
    .ToDictionary(s => s.Name);  // 姓名 → 学生对象

Console.WriteLine(dict["张三"].Score);  // 92

// ToLookup —— 类似 GroupBy,但不是延迟执行的
ILookup<string, Student> lookup = students.ToLookup(s => s.City);
foreach (var s in lookup["北京"])
    Console.WriteLine(s.Name);  // 张三, 王五, 周八

六、延迟执行(Deferred Execution)—— 极其重要!

6.1 什么是延迟执行?

结论:大部分 LINQ 方法返回的是一个"查询计划"而不是结果。只有真正用到数据时,查询才执行。

// 定义了一个查询,但此时还没有执行!
var query = students.Where(s => s.Score >= 80);

// 这时候才真正执行查询(遍历时才执行)
foreach (var s in query)
{
    Console.WriteLine(s.Name);
}

6.2 延迟执行的影响——数据源变了,结果也变

var query = students.Where(s => s.Score >= 80);

Console.WriteLine($"第一次: {query.Count()}");  // 假设 3 人

// 修改数据源!
students.Add(new Student { Name = "新人", Score = 100 });

Console.WriteLine($"第二次: {query.Count()}");  // 变成 4 人了!

6.3 立即执行 vs 延迟执行

// 延迟执行的方法(返回 IEnumerable<T>):
// Where, Select, OrderBy, GroupBy, Skip, Take, Concat, ...

// 立即执行的方法(返回具体值):
// ToList, ToArray, ToDictionary
// First, Last, Single, ElementAt
// Sum, Average, Count, Max, Min
// Any, All, Contains

// ✅ 立即执行——结果固定
var list = students.Where(s => s.Score >= 80).ToList();

students.Add(new Student { Name = "新人", Score = 100 });

Console.WriteLine(list.Count);  // 还是原来的数量,不受影响!

简单的判断规则:

  • 返回 IEnumerable<T> 的方法 → 延迟执行
  • 返回 List<T>T 单个值、intbool 等的方法 → 立即执行

6.4 延迟执行的陷阱

// ❌ 陷阱:多次遍历会多次执行查询!
var query = students.Where(s =>
{
    Console.WriteLine($"检查 {s.Name}");
    return s.Score >= 80;
});

Console.WriteLine("=== 第一次遍历 ===");
foreach (var s in query) { }  // 打印 8 次"检查..."

Console.WriteLine("=== 第二次遍历 ===");
foreach (var s in query) { }  // 又打印 8 次!

// ✅ 正确做法:先 ToList() 固化结果
var fixedList = students.Where(s => s.Score >= 80).ToList();

七、综合实战示例

7.1 完整示例:学生成绩报表

using System;
using System.Collections.Generic;
using System.Linq;

public class Student
{
    public string Name;
    public int Age;
    public int Score;
    public string City;
    public string Subject;

    public override string ToString()
    {
        return $"{Name,-6} | {Age}岁 | {City,-4} | {Subject,-4} | {Score}分";
    }
}

class Program
{
    static void Main()
    {
        List<Student> students = new List<Student>
        {
            new Student { Name = "张三", Age = 18, Score = 92, City = "北京", Subject = "数学" },
            new Student { Name = "李四", Age = 19, Score = 85, City = "上海", Subject = "数学" },
            new Student { Name = "王五", Age = 18, Score = 76, City = "北京", Subject = "语文" },
            new Student { Name = "赵六", Age = 20, Score = 58, City = "广州", Subject = "数学" },
            new Student { Name = "孙七", Age = 19, Score = 88, City = "上海", Subject = "语文" },
            new Student { Name = "周八", Age = 20, Score = 95, City = "北京", Subject = "英语" },
            new Student { Name = "吴九", Age = 18, Score = 45, City = "广州", Subject = "英语" },
            new Student { Name = "郑十", Age = 19, Score = 72, City = "深圳", Subject = "数学" },
        };

        Console.WriteLine("========== 学生成绩报表 ==========\n");

        // 1. 基本统计
        Console.WriteLine("【总体统计】");
        Console.WriteLine($"总人数: {students.Count()}");
        Console.WriteLine($"平均分: {students.Average(s => s.Score):F1}");
        Console.WriteLine($"最高分: {students.Max(s => s.Score)}");
        Console.WriteLine($"最低分: {students.Min(s => s.Score)}");
        Console.WriteLine($"及格率: {(double)students.Count(s => s.Score >= 60) / students.Count() * 100:F1}%");

        // 2. 按科目统计
        Console.WriteLine("\n【按科目统计】");
        var bySubject = students
            .GroupBy(s => s.Subject)
            .Select(g => new
            {
                Subject = g.Key,
                Count = g.Count(),
                Avg = g.Average(s => s.Score),
                Max = g.Max(s => s.Score),
                Min = g.Min(s => s.Score)
            });

        foreach (var subj in bySubject)
        {
            Console.WriteLine($"  {subj.Subject}: {subj.Count}人, "
                + $"均分{subj.Avg:F1}, 最高{subj.Max}, 最低{subj.Min}");
        }

        // 3. 按城市统计
        Console.WriteLine("\n【按城市统计】");
        var byCity = students
            .GroupBy(s => s.City)
            .Select(g => new
            {
                City = g.Key,
                Count = g.Count(),
                Avg = g.Average(s => s.Score)
            })
            .OrderByDescending(c => c.Avg);

        foreach (var city in byCity)
        {
            Console.WriteLine($"  {city.City}: {city.Count}人, 均分{city.Avg:F1}");
        }

        // 4. 光荣榜(Top 3)
        Console.WriteLine("\n【光荣榜 Top 3】");
        var top3 = students
            .OrderByDescending(s => s.Score)
            .Take(3);

        int rank = 1;
        foreach (var s in top3)
        {
            Console.WriteLine($"  {rank++}. {s}");
        }

        // 5. 需要补考的学生(不及格)
        Console.WriteLine("\n【补考名单】");
        var needRetake = students.Where(s => s.Score < 60);
        if (needRetake.Any())
        {
            foreach (var s in needRetake)
            {
                Console.WriteLine($"  {s.Name} - {s.Subject} - {s.Score}分 (差{60 - s.Score}分)");
            }
        }
        else
        {
            Console.WriteLine("  全员及格!");
        }

        // 6. 分页展示(第 1 页,每页 3 个)
        Console.WriteLine("\n【分页展示 - 按姓名排序,第 1 页】");
        int pageSize = 3;
        int currentPage = 1;
        var page1 = students
            .OrderBy(s => s.Name)
            .Skip((currentPage - 1) * pageSize)
            .Take(pageSize);

        foreach (var s in page1)
        {
            Console.WriteLine($"  {s}");
        }
    }
}

输出:

========== 学生成绩报表 ==========

【总体统计】
总人数: 8
平均分: 76.4
最高分: 95
最低分: 45
及格率: 75.0%

【按科目统计】
  数学: 4人, 均分76.8, 最高92, 最低58
  语文: 2人, 均分82.0, 最高88, 最低76
  英语: 2人, 均分70.0, 最高95, 最低45

【按城市统计】
  上海: 2人, 均分86.5
  北京: 3人, 均分87.7
  深圳: 1人, 均分72.0
  广州: 2人, 均分51.5

【光荣榜 Top 3】
  1. 周八    | 20岁 | 北京  | 英语  | 95分
  2. 张三    | 18岁 | 北京  | 数学  | 92分
  3. 孙七    | 19岁 | 上海  | 语文  | 88分

【补考名单】
  赵六 - 数学 - 58分 (差2分)
  吴九 - 英语 - 45分 (差15分)

【分页展示 - 按姓名排序,第 1 页】
  吴九    | 18岁 | 广州  | 英语  | 45分
  周八    | 20岁 | 北京  | 英语  | 95分
  孙七    | 19岁 | 上海  | 语文  | 88分

7.2 完整示例:字符串处理

string text = "C# is an amazing language. LINQ makes data query easy. C# and LINQ are perfect together.";

// 1. 分词
var words = text.Split(' ', '.')
    .Where(w => !string.IsNullOrEmpty(w));

// 2. 统计每个单词出现次数
var wordCount = words
    .GroupBy(w => w.ToLower())    // 忽略大小写分组
    .Select(g => new { Word = g.Key, Count = g.Count() })
    .OrderByDescending(x => x.Count);

Console.WriteLine("单词频率统计:");
foreach (var wc in wordCount)
{
    Console.WriteLine($"  {wc.Word}: {wc.Count}");
}

// 3. 找出所有以大写字母开头的单词
var capitalizedWords = words.Where(w => char.IsUpper(w[0]));
Console.WriteLine($"\n大写开头单词: {string.Join(", ", capitalizedWords)}");

// 4. 计算平均单词长度
double avgLength = words.Average(w => w.Length);
Console.WriteLine($"平均单词长度: {avgLength:F1}");

// 5. 最长的单词
string longest = words.OrderByDescending(w => w.Length).First();
Console.WriteLine($"最长单词: {longest} ({longest.Length}字符)");

输出:

单词频率统计:
  c#: 2
  linq: 2
  and: 2
  is: 1
  an: 1
  amazing: 1
  language: 1
  makes: 1
  data: 1
  query: 1
  easy: 1
  are: 1
  perfect: 1
  together: 1

大写开头单词: C#, LINQ, C#, LINQ
平均单词长度: 5.0
最长单词: together (8字符)

八、常用 LINQ 操作符速查表

筛选

方法 作用 示例
Where 条件过滤 .Where(x => x > 5)
OfType 按类型过滤 .OfType<string>()

投影

方法 作用 示例
Select 转换每个元素 .Select(x => x.Name)
SelectMany 扁平化嵌套集合 .SelectMany(x => x.List)

排序

方法 作用 示例
OrderBy 升序 .OrderBy(x => x.Age)
OrderByDescending 降序 .OrderByDescending(x => x.Age)
ThenBy 二级升序 .ThenBy(x => x.Name)
ThenByDescending 二级降序 .ThenByDescending(x => x.Name)
Reverse 反转 .Reverse()

分组

方法 作用 示例
GroupBy 按键分组 .GroupBy(x => x.City)
ToLookup 一对多字典 .ToLookup(x => x.City)

集合运算

方法 作用 示例
Distinct 去重 .Distinct()
Union 并集 a.Union(b)
Intersect 交集 a.Intersect(b)
Except 差集 a.Except(b)
Concat 拼接 a.Concat(b)

量词

方法 作用 示例
Any 是否存在 .Any(x => x > 5)
All 是否全部 .All(x => x > 0)
Contains 是否包含 .Contains(5)

聚合

方法 作用 示例
Count 计数 .Count()
Sum 求和 .Sum(x => x.Age)
Average 平均 .Average(x => x.Score)
Max 最大 .Max(x => x.Score)
Min 最小 .Min(x => x.Score)
Aggregate 自定义累加 .Aggregate((a,b) => a+b)

分区

方法 作用 示例
Take 取前 n 个 .Take(5)
Skip 跳过前 n 个 .Skip(5)
TakeWhile 条件取前 .TakeWhile(x => x > 0)
SkipWhile 条件跳过 .SkipWhile(x => x > 0)

元素

方法 作用 示例
First / FirstOrDefault 第一个 .First(x => x.Age > 18)
Last / LastOrDefault 最后一个 .Last()
Single / SingleOrDefault 唯一一个 .Single(x => x.Name == "张三")
ElementAt / ElementAtOrDefault 按索引 .ElementAt(2)

连接

方法 作用 示例
Join 内连接 a.Join(b, k1, k2, result)
GroupJoin 分组连接 a.GroupJoin(b, k1, k2, result)

生成

方法 作用 示例
Range 生成连续数 Enumerable.Range(1, 10)
Repeat 重复生成 Enumerable.Repeat("a", 5)
Empty 空集合 Enumerable.Empty<int>()

转换

方法 作用 示例
ToList 转为 List .ToList()
ToArray 转为数组 .ToArray()
ToDictionary 转为字典 .ToDictionary(x => x.Key)
Cast 类型转换 .Cast<BaseType>()

九、常见易错点(避坑指南)

坑1:延迟执行 + 多次遍历

// ❌ 查了两次!
var query = students.Where(s => { Console.WriteLine($"检查: {s.Name}"); return s.Score > 80; });
int count = query.Count();   // 遍历一次
var list = query.ToList();    // 又遍历一次!

// ✅ 先固化
var fixedList = students.Where(s => s.Score > 80).ToList();
int count = fixedList.Count;

坑2:FirstOrDefault 可能返回 null

// 值类型(struct)用 FirstOrDefault 会返回默认值 0,不抛异常
int[] nums = { 1, 2, 3 };
int result = nums.FirstOrDefault(x => x > 100);
Console.WriteLine(result);  // 0 ← 不是报错,是返回了 int 的默认值!

// 引用类型(class)会返回 null
Student s = students.FirstOrDefault(x => x.Score > 100);
if (s == null)
{
    Console.WriteLine("没找到");
}

坑3:Select 引用了"当前时间"等变量

// ❌ 如果 Select 中用 DateTime.Now,每个元素的时间可能不同!
var withTime = students.Select(s => new
{
    s.Name,
    Time = DateTime.Now   // 遍历时才执行,时间不一样!
});

// ✅ 先拿到时间再 Select
var now = DateTime.Now;
var withTime = students.Select(s => new
{
    s.Name,
    Time = now
});

坑4:修改 LINQ 查询结果中的对象

// 查询出来的对象还是原来的引用!
var student = students.First(s => s.Name == "张三");
student.Score = 100;

// 原始列表里的张三也变成了 100 分!
Console.WriteLine(students[0].Score);  // 100

十、总结

概念 说明
LINQ 是什么 C# 内置的集合查询工具,像写 SQL 一样操作数据
两种写法 方法语法(.Where().Select())和查询语法(from where select
延迟执行 大部分方法返回查询计划,遍历时才执行,用 ToList() 可立即执行
核心模式 筛选 → 排序 → 投影 → 聚合,链式调用像搭积木

一句话总结:LINQ 让你用链式调用的方式操作数据集合,告别手写循环和临时变量。记住:Where 筛选 → OrderBy 排序 → Select 转换 → ToList 执行,这是最常用的四步组合。

0
博主关闭了当前页面的评论