解惑LINQ中的SelectMany用法

发布于:2025-07-20 ⋅ 阅读:(12) ⋅ 点赞:(0)

在 C# 中,SelectMany 是 LINQ(语言集成查询) 中的一个强大方法,用于将嵌套的集合 “扁平化”(Flatten),并对每个元素应用转换函数。它在处理多维数据结构时特别有用,例如从集合的集合中提取单个元素序列。

1. 基本概念

  • 作用:将一个包含多个集合的集合展开为一个单级集合,并可对每个元素应用转换。
  • 适用场景:处理嵌套数据结构(如列表的列表、数组的数组),或需要跨集合查询元素。

2. 核心重载方法

(1)无转换函数的重载
IEnumerable<TResult> SelectMany<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, IEnumerable<TResult>> selector
);
  • 参数
    • source:源集合(如 List<List<int>>)。
    • selector:将每个源元素映射为一个子集合的函数。
  • 返回值:所有子集合合并后的单级序列。
(2)带索引的重载
IEnumerable<TResult> SelectMany<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, int, IEnumerable<TResult>> selector
);
  • 特点selector 函数的第二个参数为元素索引,可用于更复杂的转换。
(3)带结果选择器的重载
IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, IEnumerable<TCollection>> collectionSelector,
    Func<TSource, TCollection, TResult> resultSelector
);
  • 特点:通过 resultSelector 组合源元素和子集合元素,生成最终结果。

3. 示例:无转换函数的扁平化

假设我们有一个包含多个列表的列表,需要将其展开为一个单级列表:

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

// 使用 SelectMany 扁平化
IEnumerable<int> flattened = nestedList.SelectMany(x => x);

// 输出:1, 2, 3, 4, 5, 6
foreach (int num in flattened)
{
    Console.Write(num + ", ");
}

4. 示例:带转换函数的 SelectMany

假设有一个 Person 类,每个 Person 有多个 Address,需要提取所有地址的城市:

class Person
{
    public string Name { get; set; }
    public List<Address> Addresses { get; set; }
}

class Address
{
    public string City { get; set; }
}

// 创建数据
List<Person> people = new List<Person>
{
    new Person
    {
        Name = "Alice",
        Addresses = new List<Address>
        {
            new Address { City = "New York" },
            new Address { City = "London" }
        }
    },
    new Person
    {
        Name = "Bob",
        Addresses = new List<Address>
        {
            new Address { City = "Paris" }
        }
    }
};

// 提取所有城市(扁平化并转换)
IEnumerable<string> cities = people.SelectMany(p => p.Addresses.Select(a => a.City));

// 输出:New York, London, Paris
foreach (string city in cities)
{
    Console.Write(city + ", ");
}

5. 示例:带索引的 SelectMany

使用元素索引生成更复杂的结果:

string[] sentences = { "Hello world", "Goodbye moon" };

// 每个单词前加上其所在句子的索引
var result = sentences.SelectMany((sentence, index) =>
    sentence.Split(' ').Select(word => $"[{index}]: {word}")
);

// 输出:[0]: Hello, [0]: world, [1]: Goodbye, [1]: moon
foreach (var item in result)
{
    Console.Write(item + ", ");
}

6. 示例:带结果选择器的 SelectMany

组合源元素和子集合元素:

List<string> words = new List<string> { "ABC", "DEF" };

// 结果选择器:将每个字符与原单词组合
var result = words.SelectMany(
    word => word,  // 子集合选择器:将单词拆分为字符
    (word, character) => $"({word}, {character})"  // 结果选择器
);

// 输出:(ABC, A), (ABC, B), (ABC, C), (DEF, D), (DEF, E), (DEF, F)
foreach (var item in result)
{
    Console.Write(item + ", ");
}

7. SelectMany vs Select

方法 作用 结果类型
Select 对每个元素应用转换函数,返回与源集合数量相同的新集合。 IEnumerable<TResult>
SelectMany 将嵌套集合展开并应用转换,返回合并后的单级集合(数量可能多于源集合)。 IEnumerable<TResult>(扁平化)

8. 应用场景

  1. 数据库查询
    在 EF Core 中查询嵌套导航属性(如 Orders.SelectMany(o => o.Items))。

  2. 文本处理
    将多行文本拆分为单词集合。

  3. 事件处理
    将多个事件源的事件合并为一个流。

  4. 并行计算
    将任务集合展开为单个任务序列执行。

9. 性能注意事项

  • SelectMany 是延迟执行的,直到迭代时才会真正展开集合。
  • 对于深层嵌套的集合(如三维数组),多次调用 SelectMany 可能影响性能,可考虑分阶段处理。

总结

SelectMany 是处理嵌套数据结构的核心工具,它通过 “扁平化 + 转换” 的组合,让开发者可以简洁地操作复杂数据。理解其重载方法的差异(尤其是带结果选择器的版本),能帮助你更灵活地处理各种数据场景。


网站公告

今日签到

点亮在社区的每一天
去签到