C# SelectMany 完全指南:从入门到精通
大家好!今天我们来深入探讨C#中一个非常强大但经常被初学者忽视的方法:SelectMany。作为一个热爱分享的程序员,我来和大家分享!
🎯 SelectMany 是什么?
简单来说,SelectMany就是处理"集合的集合"的利器。它可以把**多层嵌套的集合"拍平"**成一个单层集合。
基础概念对比
让我们先通过一个表格理解Select和SelectMany的区别:
| 方法 | 输入 → 输出 | 形象比喻 | 结果结构 |
|---|---|---|---|
| Select | List<A> → List<B> |
给每个苹果贴标签 🍎→ 🏷️ | 保持原有层级 |
| SelectMany | List<List<A>> → List<A> |
打开多个水果篮 🧺🧺→ 🍎🍊🍌 | 减少嵌套层级 |
🔍 基础语法解析
csharp
// SelectMany 方法签名
public static IEnumerable<TResult> SelectMany<TSource, TResult>(
this IEnumerable<TSource> source, // 源集合
Func<TSource, IEnumerable<TResult>> selector // 选择器函数
)
// 重载版本(包含结果选择器)
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(
this IEnumerable<TSource> source, // 源集合
Func<TSource, IEnumerable<TCollection>> selector, // 集合选择器
Func<TSource, TCollection, TResult> resultSelector // 结果选择器
)
📚 实际应用示例
示例1:基础使用 - 展开学生和课程
csharp
using System;
using System.Collections.Generic;
using System.Linq;
// 定义数据模型
public class Student
{
public string Name { get; set; }
public List<string> Courses { get; set; } // 每个学生有多个课程
}
class Program
{
static void Main()
{
// 创建测试数据
var students = new List<Student>
{
new Student { Name = "张三", Courses = new List<string> { "数学", "英语", "物理" } },
new Student { Name = "李四", Courses = new List<string> { "语文", "历史" } },
new Student { Name = "王五", Courses = new List<string> { "化学", "生物", "地理" } }
};
Console.WriteLine("=== 学生选课情况 ===");
// 方法1:使用 Select - 得到的是"集合的集合"
var coursesWithSelect = students.Select(s => s.Courses);
Console.WriteLine("\n1. 使用 Select 结果:");
foreach (var courseList in coursesWithSelect)
{
// courseList 是 List<string>,需要再次遍历
foreach (var course in courseList)
{
Console.WriteLine($" - {course}");
}
}
// 方法2:使用 SelectMany - 直接得到"拍平"的课程列表
var coursesWithSelectMany = students.SelectMany(s => s.Courses);
Console.WriteLine("\n2. 使用 SelectMany 结果:");
foreach (var course in coursesWithSelectMany)
{
// 直接遍历课程字符串,不需要嵌套循环
Console.WriteLine($" - {course}");
}
// 方法3:SelectMany 带索引 - 显示学生和课程的对应关系
var studentCourses = students.SelectMany(
student => student.Courses, // 选择课程集合
(student, course) => new // 结果选择器:组合学生和课程信息
{
StudentName = student.Name,
CourseName = course
}
);
Console.WriteLine("\n3. 学生-课程对应关系:");
foreach (var item in studentCourses)
{
Console.WriteLine($" {item.StudentName} 选择了 {item.CourseName}");
}
}
}
输出结果:
=== 学生选课情况 ===
1. 使用 Select 结果:
- 数学
- 英语
- 物理
- 语文
- 历史
- 化学
- 生物
- 地理
2. 使用 SelectMany 结果:
- 数学
- 英语
- 物理
- 语文
- 历史
- 化学
- 生物
- 地理
3. 学生-课程对应关系:
张三 选择了 数学
张三 选择了 英语
张三 选择了 物理
李四 选择了 语文
李四 选择了 历史
王五 选择了 化学
王五 选择了 生物
王五 选择了 地理
示例2:EF Core 中的联表查询
csharp
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
// 实体类
public class Department
{
public int DepartmentId { get; set; }
public string DepartmentName { get; set; }
public List<Employee> Employees { get; set; } // 一个部门有多个员工
}
public class Employee
{
public int EmployeeId { get; set; }
public string EmployeeName { get; set; }
public string Position { get; set; }
public int DepartmentId { get; set; }
public Department Department { get; set; }
}
public class CompanyContext : DbContext
{
public DbSet<Department> Departments { get; set; }
public DbSet<Employee> Employees { get; set; }
}
class Program
{
static void Main()
{
using var context = new CompanyContext();
Console.WriteLine("=== EF Core 中使用 SelectMany ===");
// 场景1:获取所有部门的所有员工(扁平化)
var allEmployees = context.Departments
.SelectMany(dept => dept.Employees) // 将各部门的员工集合合并成一个集合
.ToList();
Console.WriteLine("\n1. 公司所有员工:");
foreach (var emp in allEmployees)
{
Console.WriteLine($" - {emp.EmployeeName} ({emp.Position})");
}
// 场景2:使用结果选择器 - 获取员工及其部门信息
var employeesWithDept = context.Departments
.SelectMany(
dept => dept.Employees, // 选择员工集合
(dept, emp) => new // 结果选择器:组合部门和员工信息
{
DepartmentName = dept.DepartmentName,
EmployeeName = emp.EmployeeName,
Position = emp.Position
}
)
.ToList();
Console.WriteLine("\n2. 员工及所属部门:");
foreach (var item in employeesWithDept)
{
Console.WriteLine($" {item.DepartmentName} - {item.EmployeeName} ({item.Position})");
}
// 场景3:结合 Where 条件过滤
var managersOnly = context.Departments
.SelectMany(
dept => dept.Employees.Where(emp => emp.Position.Contains("经理")),
(dept, emp) => new
{
DepartmentName = dept.DepartmentName,
ManagerName = emp.EmployeeName,
Position = emp.Position
}
)
.ToList();
Console.WriteLine("\n3. 各部门经理:");
foreach (var manager in managersOnly)
{
Console.WriteLine($" {manager.DepartmentName}: {manager.ManagerName} ({manager.Position})");
}
}
}
示例3:处理多层嵌套数据
csharp
using System;
using System.Collections.Generic;
using System.Linq;
// 多层嵌套的数据结构
public class Company
{
public string CompanyName { get; set; }
public List<Department> Departments { get; set; }
}
public class Department
{
public string DeptName { get; set; }
public List<Team> Teams { get; set; }
}
public class Team
{
public string TeamName { get; set; }
public List<Employee> Members { get; set; }
}
public class Employee
{
public string Name { get; set; }
public string Email { get; set; }
}
class Program
{
static void Main()
{
// 创建测试数据
var company = new Company
{
CompanyName = "科技公司",
Departments = new List<Department>
{
new Department
{
DeptName = "技术部",
Teams = new List<Team>
{
new Team
{
TeamName = "前端团队",
Members = new List<Employee>
{
new Employee { Name = "前端张三", Email = "zhangsan@email.com" },
new Employee { Name = "前端李四", Email = "lisi@email.com" }
}
},
new Team
{
TeamName = "后端团队",
Members = new List<Employee>
{
new Employee { Name = "后端王五", Email = "wangwu@email.com" },
new Employee { Name = "后端赵六", Email = "zhaoliu@email.com" }
}
}
}
},
new Department
{
DeptName = "市场部",
Teams = new List<Team>
{
new Team
{
TeamName = "营销团队",
Members = new List<Employee>
{
new Employee { Name = "营销钱七", Email = "qianqi@email.com" }
}
}
}
}
}
};
Console.WriteLine("=== 处理多层嵌套数据 ===\n");
// 使用多个 SelectMany 展开多层嵌套
var allEmployees = company.Departments
.SelectMany(dept => dept.Teams) // 第一层:部门 → 团队
.SelectMany(team => team.Members) // 第二层:团队 → 成员
.ToList();
Console.WriteLine("1. 公司所有员工:");
foreach (var emp in allEmployees)
{
Console.WriteLine($" - {emp.Name} ({emp.Email})");
}
// 单次查询完成多层展开(使用结果选择器保持上下文)
var employeesWithStructure = company.Departments
.SelectMany(
dept => dept.Teams, // 部门展开为团队
(dept, team) => new { dept, team } // 保留部门和团队信息
)
.SelectMany(
combo => combo.team.Members, // 团队展开为成员
(combo, employee) => new // 组合所有信息
{
Department = combo.dept.DeptName,
Team = combo.team.TeamName,
EmployeeName = employee.Name,
Email = employee.Email
}
)
.ToList();
Console.WriteLine("\n2. 员工完整组织架构:");
foreach (var item in employeesWithStructure)
{
Console.WriteLine($" {item.Department} -> {item.Team} -> {item.EmployeeName}");
}
}
}
💡 SelectMany 的高级用法
用法1:交叉连接(Cartesian Product)
csharp
using System;
using System.Linq;
class Program
{
static void Main()
{
var colors = new[] { "红", "蓝", "绿" };
var sizes = new[] { "S", "M", "L" };
// 使用 SelectMany 实现交叉连接
var products = colors.SelectMany(
color => sizes, // 为每个颜色匹配所有尺寸
(color, size) => $"{color}色-{size}码" // 组合结果
);
Console.WriteLine("=== 颜色和尺寸的交叉组合 ===");
foreach (var product in products)
{
Console.WriteLine($" {product}");
}
// 等价于嵌套循环:
// foreach (var color in colors)
// foreach (var size in sizes)
// Console.WriteLine($"{color}色-{size}码");
}
}
用法2:处理可能为空的集合
csharp
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
var students = new List<Student>
{
new Student { Name = "张三", Courses = new List<string> { "数学", "英语" } },
new Student { Name = "李四", Courses = null }, // 课程列表为null
new Student { Name = "王五", Courses = new List<string>() } // 空课程列表
};
// 安全的 SelectMany - 处理null集合
var allCourses = students
.Where(s => s.Courses != null) // 过滤掉null集合
.SelectMany(s => s.Courses) // 展开课程
.DefaultIfEmpty("无课程") // 如果没有课程,提供默认值
.ToList();
Console.WriteLine("=== 所有课程(安全处理) ===");
foreach (var course in allCourses)
{
Console.WriteLine($" - {course}");
}
// 更简洁的写法:使用空集合合并运算符
var safeCourses = students
.SelectMany(s => s.Courses ?? Enumerable.Empty<string>())
.ToList();
}
}
🚀 性能优化建议
-
与 Select 的对比 :对于单层集合,
Select性能更好;对于嵌套集合,SelectMany更合适 -
延迟执行 :
SelectMany也是延迟执行的,只有在遍历结果时才会真正执行 -
数据库查询 :在EF Core中,
SelectMany会被翻译成SQL的JOIN语句,在数据库层面执行
csharp
// EF Core 中的高效用法
var efficientQuery = context.Departments
.Where(dept => dept.IsActive)
.SelectMany(dept => dept.Employees.Where(emp => emp.IsActive))
.Select(emp => new { emp.Name, emp.Salary })
.ToList();
📊 总结
| 场景 | 推荐方法 | 优点 |
|---|---|---|
| 单层集合转换 | Select |
简单直观,性能好 |
| 嵌套集合展开 | SelectMany |
代码简洁,避免嵌套循环 |
| 数据库联表查询 | SelectMany |
生成高效SQL,减少数据库往返 |
| 复杂数据转换 | SelectMany + 结果选择器 |
保持数据关联,灵活组合 |
核心要点:
SelectMany是处理"集合的集合"的最佳工具- 它可以把多层嵌套的数据结构"拍平"成单层结构
- 在EF Core中,它通常对应SQL的
JOIN操作 - 使用结果选择器可以保持原始数据的上下文信息
希望这篇详细的教程能帮助你彻底掌握SelectMany的用法!在实际开发中多多练习,你会发现它的强大之处。Happy coding! 🎉