当前位置: 首页 > news >正文

C# SelectMany 完全指南:从入门到精通

C# SelectMany 完全指南:从入门到精通

大家好!今天我们来深入探讨C#中一个非常强大但经常被初学者忽视的方法:SelectMany。作为一个热爱分享的程序员,我来和大家分享!

🎯 SelectMany 是什么?

简单来说,SelectMany就是处理"集合的集合"的利器。它可以把**多层嵌套的集合"拍平"**成一个单层集合。

基础概念对比

让我们先通过一个表格理解SelectSelectMany的区别:

方法输入 → 输出形象比喻结果结构
SelectList<A>List<B>给每个苹果贴标签 🍎→ 🏷️保持原有层级
SelectManyList<List<A>>List<A>打开多个水果篮 🧺🧺→ 🍎🍊🍌减少嵌套层级

🔍 基础语法解析

// 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:基础使用 - 展开学生和课程

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 中的联表查询

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:处理多层嵌套数据

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)

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:处理可能为空的集合

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 },  // 课程列表为nullnew 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();}
}

🚀 性能优化建议

  1. 与 Select 的对比:对于单层集合,Select性能更好;对于嵌套集合,SelectMany更合适

  2. 延迟执行SelectMany也是延迟执行的,只有在遍历结果时才会真正执行

  3. 数据库查询:在EF Core中,SelectMany会被翻译成SQL的JOIN语句,在数据库层面执行

// 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! 🎉

http://www.dtcms.com/a/544629.html

相关文章:

  • 卡片式设计网站制作婚庆网站建设需求分析
  • RK3399 11.0关闭调试串口改为普通RS232通信串口
  • 手机网站弹窗大唐网站建设
  • 播放本地音频的代码
  • cefsharp139-H264-X86升级测试(MP4)-支持PDF预览-chromium7258定制浏览器
  • pandoc导出markdown为PDF,同时解决中文内容报乱码的错误
  • 【printpdf】生成PDF的全能Rust库printpdf
  • 小技巧:ipynb转pdf
  • 计算机网络自顶向下方法16——应用层 因特网视频 HTTP流和DASH
  • 摄像头选型与对应采集工具方案
  • 免费的行情软件下载安装佛山网站优化指导
  • 仓颉尾递归优化:从编译器实现到函数式编程实践
  • 小智机器人连接抖音直播间教程
  • webhooks
  • 基于Springboot + vue3实现的亚运会志愿者管理系统
  • 绥中做网站百度如何网站
  • 双碳主题互动装置-低碳环保互动游戏-VR环保展厅方案
  • AI重构兴趣内容与营销生态,驱动消费全链路升级
  • 【数据结构】从线性表到排序算法详解
  • 网站家建设培训学校设计科技公司官网
  • SPIR-V后端稳定性的推进工作报告总结
  • MySQL逗号分隔字段-历史遗留原因兼容方案
  • Bun.js + Elysia 框架实现基于 SQLITE3 的简单 CURD 后端服务
  • 做网站 怎么赚钱吗网站数据分析课程
  • Rust——迭代器适配器深度解析:函数式编程的优雅实践
  • 理解PostgreSQL中的映射表
  • Java1029 抽象类:构造方法
  • 类和对象(中)——日期类的实现取地址运算符重载
  • Linux系统编程—线程同步与互斥
  • 【笔试真题】- 百度第一套-2025.09.23