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();
这段代码的意思非常直观:
- 先过滤出大于 3 的数
- 再把每个数乘以 10
- 最后转成列表
第三章 最常用的 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 Any 和 All
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() 时。
第七章 IEnumerable 和 IQueryable 的区别
这部分在写集合处理和数据库查询时非常关键。
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 小心 Contains、GroupBy、复杂嵌套查询
这些操作要关注最终生成的 SQL 是否合理。
第十一章 什么时候不用 LINQ
虽然 LINQ 很好用,但以下情况不一定适合强行用:
- 超复杂报表统计
- 极其依赖数据库特性的 SQL
- 特别强调极限性能的查询
这时候可以考虑:
- 手写 SQL
EF Core FromSqlDapper
第十二章 入门阶段建议掌握到什么程度
如果你刚开始学 LINQ,建议至少做到:
- 会使用
Where、Select、OrderBy、Any、FirstOrDefault - 能理解
GroupBy和投影查询 - 知道延迟执行和立即执行的区别
- 知道
IEnumerable和IQueryable的基本差异 - 能识别过早
ToList()、重复枚举等常见问题
延伸阅读
- 学完语言基础后,建议先阅读
C# 基础入门 - 如果你要把
LINQ用到数据库查询中,继续阅读EF Core 基础入门 - 如果你要把这些查询结果输出到接口层,继续阅读
ASP.NET Core 基础
本文小结
集合和 LINQ 是 C# 开发里最核心的日常能力之一。真正掌握它,不只是会写几个链式调用,而是要建立这几层认知:
- 集合类型如何选择
- 查询逻辑如何组合
- 查询什么时候执行
- 查询写法如何兼顾可读性和性能
一旦这部分打稳,你写业务代码和数据库查询的质量都会明显提升。