C# 集合与 LINQ 实战

系统讲解 C# 常用集合与 LINQ 查询,涵盖过滤、投影、分组、排序、延迟执行与常见性能误区。

C# 集合与 LINQ 实战

如果说 C# 基础决定你能不能把代码写出来,那么集合和 LINQ 决定你能不能把数据处理写得清晰、可维护、可扩展。真实项目里,用户列表、订单明细、权限点、日志记录、配置项、数据库查询结果,几乎都离不开集合和 LINQ

很多初学者会觉得 LINQ “看起来很好用”,但经常停留在 Where + Select 这个层面。真正进入项目后,还需要理解:

  • 不同集合类型怎么选
  • LINQ 到底什么时候执行
  • 查询链怎么拆才更可读
  • 哪些写法可能带来性能问题

第一章 常见集合怎么选

1.1 数组

数组适合长度固定、结构简单的场景。

int[] numbers = { 1, 2, 3, 4, 5 };
Console.WriteLine(numbers[0]);
Console.WriteLine(numbers.Length);

特点:

  • 长度固定
  • 访问快
  • 不适合频繁增删

1.2 List<T>

这是业务代码里最常见的集合类型。

var users = new List<string> { "Tom", "Alice", "Bob" };
users.Add("Jerry");
users.Remove("Tom");

适合:

  • 顺序数据
  • 动态增删
  • 列表展示

1.3 Dictionary<TKey, TValue>

适合做“键 -> 值”的快速查找。

var dict = new Dictionary<int, string>
{
    [1] = "Tom",
    [2] = "Alice"
};

Console.WriteLine(dict[1]);

适合:

  • ID 映射名称
  • 配置项缓存
  • 去重后快速取值

1.4 HashSet<T>

适合做去重和成员判断。

var permissions = new HashSet<string>
{
    "users.read",
    "users.write",
    "users.read"
};

Console.WriteLine(permissions.Count); // 2

第二章 LINQ 到底解决什么问题

LINQ 全称是 Language Integrated Query,也就是“语言集成查询”。它的核心价值不是“写得炫”,而是:

  • 用统一风格处理集合数据
  • 让过滤、排序、投影、分组等操作可组合
  • 提高表达力和可读性

最基础的一个例子:

var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };

var result = numbers
    .Where(x => x > 3)
    .Select(x => x * 10)
    .ToList();

这段代码的意思非常直观:

  1. 先过滤出大于 3 的数
  2. 再把每个数乘以 10
  3. 最后转成列表

第三章 最常用的 LINQ 操作

3.1 Where 过滤

var activeUsers = users.Where(x => x.IsActive).ToList();

3.2 Select 投影

var names = users.Select(x => x.Name).ToList();

3.3 OrderBy 排序

var sorted = users.OrderBy(x => x.Name).ToList();

倒序:

var sorted = users.OrderByDescending(x => x.CreatedAt).ToList();

3.4 FirstOrDefault

var admin = users.FirstOrDefault(x => x.Role == "Admin");

要注意:

  • 找不到时返回默认值
  • 引用类型通常是 null

3.5 AnyAll

bool hasAdmin = users.Any(x => x.Role == "Admin");
bool allActive = users.All(x => x.IsActive);

3.6 Count

int activeCount = users.Count(x => x.IsActive);

3.7 GroupBy

var groups = users.GroupBy(x => x.Role);

更常见的写法:

var result = users
    .GroupBy(x => x.Role)
    .Select(g => new
    {
        Role = g.Key,
        Count = g.Count()
    })
    .ToList();

第四章 一个更贴近业务的完整例子

先准备一组测试数据:

var users = new List<UserItem>
{
    new() { Id = 1, Name = "Tom", Role = "Admin", IsActive = true, Score = 95 },
    new() { Id = 2, Name = "Alice", Role = "User", IsActive = true, Score = 88 },
    new() { Id = 3, Name = "Bob", Role = "User", IsActive = false, Score = 72 },
    new() { Id = 4, Name = "Jerry", Role = "Admin", IsActive = true, Score = 91 }
};

public class UserItem
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public string Role { get; set; } = string.Empty;
    public bool IsActive { get; set; }
    public int Score { get; set; }
}

4.1 查出启用状态用户

var activeUsers = users
    .Where(x => x.IsActive)
    .ToList();

4.2 只取接口返回需要的字段

var list = users
    .Where(x => x.IsActive)
    .Select(x => new
    {
        x.Id,
        x.Name
    })
    .ToList();

4.3 统计每种角色人数

var roleStats = users
    .GroupBy(x => x.Role)
    .Select(g => new
    {
        Role = g.Key,
        Count = g.Count()
    })
    .ToList();

4.4 取分数最高的用户

var topUser = users
    .OrderByDescending(x => x.Score)
    .FirstOrDefault();

第五章 查询语法和方法语法

LINQ 有两种风格:

5.1 方法语法

var result = users
    .Where(x => x.IsActive)
    .OrderBy(x => x.Name)
    .Select(x => x.Name)
    .ToList();

5.2 查询表达式语法

var result =
    (from u in users
     where u.IsActive
     orderby u.Name
     select u.Name).ToList();

实际开发里,方法语法更常见,尤其是在链式组合和数据库查询中。


第六章 延迟执行是什么

这是 LINQ 最重要也最容易被忽视的知识点之一。

看这个例子:

var query = users.Where(x => x.IsActive);

到这一步时,很多 LINQ 查询其实还没有真正执行,只是先构建了查询逻辑。直到你:

  • ToList()
  • ToArray()
  • Count()
  • FirstOrDefault()
  • Any()

这种“真正取结果”的操作出现时,查询才会执行。

6.1 为什么这很重要

因为如果你不理解延迟执行,就会遇到这些问题:

  • 明明改了源集合,结果却变了
  • 一个查询被重复执行多次
  • 以为已经拿到结果,实际上还没执行

6.2 一个例子

var query = users.Where(x => x.IsActive);
users.Add(new UserItem { Id = 5, Name = "New", Role = "User", IsActive = true, Score = 80 });

var result = query.ToList();

这里 result 里会包含后加进去的用户,因为真正执行发生在 ToList() 时。


第七章 IEnumerableIQueryable 的区别

这部分在写集合处理和数据库查询时非常关键。

7.1 IEnumerable<T>

通常表示内存中的可枚举集合。

7.2 IQueryable<T>

通常用于数据库查询等“可翻译查询表达式”的场景。

例如在 EF Core 里:

var query = dbContext.Users.Where(x => x.IsActive);

这时它通常不是“马上把所有数据取出来再过滤”,而是先构建表达式,后续翻译成 SQL。

7.3 为什么要知道这件事

因为下面两种写法语义可能差很多:

var users = dbContext.Users.Where(x => x.IsActive).ToList();

var users = dbContext.Users.ToList().Where(x => x.IsActive);

前者一般在数据库中过滤,后者往往是先整表拉到内存再过滤,性能差异可能很大。


第八章 常见可读性优化技巧

8.1 不要把查询链写得过长

不推荐:

var result = users.Where(x => x.IsActive).Where(x => x.Score > 80).OrderByDescending(x => x.Score).Select(x => new { x.Id, x.Name, x.Role }).ToList();

更推荐:

var result = users
    .Where(x => x.IsActive)
    .Where(x => x.Score > 80)
    .OrderByDescending(x => x.Score)
    .Select(x => new
    {
        x.Id,
        x.Name,
        x.Role
    })
    .ToList();

8.2 复杂条件拆变量

bool IsValidUser(UserItem user) =>
    user.IsActive && user.Score >= 80;

var result = users.Where(IsValidUser).ToList();

这能显著提升代码可读性。


第九章 常见性能误区

9.1 重复枚举

var query = users.Where(x => x.IsActive);

var count = query.Count();
var list = query.ToList();

这里查询可能会执行两次。

如果你明确需要复用结果,可以先物化:

var list = users.Where(x => x.IsActive).ToList();
var count = list.Count;

9.2 过早 ToList()

错误示例:

var list = dbContext.Users.ToList()
    .Where(x => x.IsActive)
    .Select(x => x.Name)
    .ToList();

更合理:

var list = dbContext.Users
    .Where(x => x.IsActive)
    .Select(x => x.Name)
    .ToList();

9.3 无脑链式调用导致可读性变差

LINQ 很强,但不是越长越好。太长的表达式要考虑拆分。


第十章 在 EF Core 中使用 LINQ 要格外注意什么

在内存集合上写 LINQ,和在 EF Core 上写 LINQ,思维不能完全一样。

10.1 不是什么方法都能翻译成 SQL

某些本地方法、复杂逻辑可能无法翻译,最终报错或退回客户端执行。

10.2 投影优先于整实体查询

如果接口只需要几个字段,优先:

var users = await dbContext.Users
    .Select(x => new UserListItem
    {
        Id = x.Id,
        Name = x.Name
    })
    .ToListAsync();

10.3 小心 ContainsGroupBy、复杂嵌套查询

这些操作要关注最终生成的 SQL 是否合理。


第十一章 什么时候不用 LINQ

虽然 LINQ 很好用,但以下情况不一定适合强行用:

  • 超复杂报表统计
  • 极其依赖数据库特性的 SQL
  • 特别强调极限性能的查询

这时候可以考虑:

  • 手写 SQL
  • EF Core FromSql
  • Dapper

第十二章 入门阶段建议掌握到什么程度

如果你刚开始学 LINQ,建议至少做到:

  1. 会使用 WhereSelectOrderByAnyFirstOrDefault
  2. 能理解 GroupBy 和投影查询
  3. 知道延迟执行和立即执行的区别
  4. 知道 IEnumerableIQueryable 的基本差异
  5. 能识别过早 ToList()、重复枚举等常见问题

延伸阅读

  • 学完语言基础后,建议先阅读 C# 基础入门
  • 如果你要把 LINQ 用到数据库查询中,继续阅读 EF Core 基础入门
  • 如果你要把这些查询结果输出到接口层,继续阅读 ASP.NET Core 基础

本文小结

集合和 LINQC# 开发里最核心的日常能力之一。真正掌握它,不只是会写几个链式调用,而是要建立这几层认知:

  • 集合类型如何选择
  • 查询逻辑如何组合
  • 查询什么时候执行
  • 查询写法如何兼顾可读性和性能

一旦这部分打稳,你写业务代码和数据库查询的质量都会明显提升。