针对嵌套IGrouping的类SelectMany扩展方法实现方案
问题描述
对一个 enumerable 连续调用两次.GroupBy()后,会得到嵌套的IGrouping集合:
IEnumerable<IGrouping<TOuterKey, IGrouping<TInnerKey, TElement>>> source = enumerable.GroupBy(o => o.InnerKey).GroupBy(g => g.OuterKey);
普通嵌套IEnumerable只需调用.SelectMany()就能扁平化,但IGrouping的扁平化实现起来没那么简单。我想编写如下通用扩展方法,请问该如何实现?
public static IEnumerable<IGrouping<TResultKey, TResultElement>> SelectMany<TOuterKey, TInnerKey, TInputElement, TResultKey, TResultElement>( this IEnumerable<IGrouping<TOuterKey, IGrouping<TInnerKey, TInputElement>>> source, Func<IGrouping<TOuterKey, IGrouping<TInnerKey, TInputElement>>, TResultKey> keySelector, Func<TOuterKey, TInnerKey, TInputElement, TResultElement> valueSelector)
实现方案
要实现这个扩展方法,核心难点在于IGrouping<TKey, TElement>是接口,.NET框架内部的分组实现类无法直接实例化,因此我们需要先自定义一个IGrouping的实现类,再完成扩展方法的逻辑:
1. 自定义IGrouping实现类
internal class Grouping<TKey, TElement> : IGrouping<TKey, TElement> { public TKey Key { get; } private readonly IEnumerable<TElement> _elements; public Grouping(TKey key, IEnumerable<TElement> elements) { Key = key; _elements = elements; } public IEnumerator<TElement> GetEnumerator() => _elements.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }
2. 实现扩展方法
public static IEnumerable<IGrouping<TResultKey, TResultElement>> SelectMany<TOuterKey, TInnerKey, TInputElement, TResultKey, TResultElement>( this IEnumerable<IGrouping<TOuterKey, IGrouping<TInnerKey, TInputElement>>> source, Func<IGrouping<TOuterKey, IGrouping<TInnerKey, TInputElement>>, TResultKey> keySelector, Func<TOuterKey, TInnerKey, TInputElement, TResultElement> valueSelector) { // 展开嵌套分组,提取所有元素及其关联的外层键、内层键,同时计算结果键 var flattenedItems = source.SelectMany(outerGroup => outerGroup.SelectMany(innerGroup => innerGroup.Select(element => new { OuterKey = outerGroup.Key, InnerKey = innerGroup.Key, InputElement = element, ResultKey = keySelector(outerGroup) }))); // 按结果键重新分组,并用自定义Grouping类包装返回 return flattenedItems .GroupBy(item => item.ResultKey, item => valueSelector(item.OuterKey, item.InnerKey, item.InputElement)) .Select(group => new Grouping<TResultKey, TResultElement>(group.Key, group)); }
逻辑说明
- 展开嵌套分组:通过两层
SelectMany把外层分组、内层分组完全展开,同时为每个元素关联外层键、内层键,以及通过keySelector计算出的结果键。 - 重新分组与包装:用展开后的元素按结果键分组,再将每个分组转换为自定义的
Grouping实例,最终返回符合要求的IEnumerable<IGrouping<TResultKey, TResultElement>>。
内容的提问来源于stack exchange,提问作者Philip Atz
相关产品推荐
相关产品推荐

