在 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. 应用场景
数据库查询:
在 EF Core 中查询嵌套导航属性(如Orders.SelectMany(o => o.Items)
)。文本处理:
将多行文本拆分为单词集合。事件处理:
将多个事件源的事件合并为一个流。并行计算:
将任务集合展开为单个任务序列执行。
9. 性能注意事项
SelectMany
是延迟执行的,直到迭代时才会真正展开集合。- 对于深层嵌套的集合(如三维数组),多次调用
SelectMany
可能影响性能,可考虑分阶段处理。
总结
SelectMany
是处理嵌套数据结构的核心工具,它通过 “扁平化 + 转换” 的组合,让开发者可以简洁地操作复杂数据。理解其重载方法的差异(尤其是带结果选择器的版本),能帮助你更灵活地处理各种数据场景。