C# Linq SelectMany用法学习

C# Linq SelectMany用法学习


C# Linq Select


public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
List<Person> persons = new List<Person>()
{
    new Person() { Name="A",Age=10 },
    new Person() { Name="B",Age=11 },
    new Person() { Name="C",Age=12 },
};
List<string> namelst = persons.Select(x => x.Name).ToArray().ToList();
Console.WriteLine(string.Join("", namelst));

输出结果为 A,B,C


C# Linq SelectMany


如果代码是这样,该如何输出Name呢?

public class Comp
{
    public string CompName { get; set; }
    public List<Person> Emps { get; set; }
}

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
List<Comp> comps = new List<Comp>()
{
    new Comp() { CompName="公司1", Emps = new List<Person>() { new Person() { Name = "A1",Age=10 },new Person() { Name="A2",Age=11 } } },
    new Comp() { CompName="公司2", Emps = new List<Person>() { new Person() { Name = "B1",Age=10 },new Person() { Name="B2",Age=11 } } },
};

一般情况下,就简单的搞2个foreach就好了,例如

// 方法1
List<string> namelst = new List<string>();
foreach (var comp in comps)
{
    foreach (var emp in comp.Emps)
    {
        namelst.Add(emp.Name);
    }
}
Console.WriteLine(string.Join("", namelst));
List<string> namelst = new List<string>();
// linq 简写
comps.ForEach(x => x.Emps.ForEach(y => namelst.Add(y.Name)));

那能不能一次搞定不需要foreach呢?面向百度编程,然后找到了SelectMany。由于未使用过,所以学习记录下。

// SelectMany 一句话搞定
List<string> namelst = comps.SelectMany(x => x.Emps).Select(x => x.Name).ToArray().ToList();

分析


var test = comps.SelectMany(x => x.Emps);
Console.WriteLine(JsonConvert.SerializeObject(test));
Console.ReadKey();

结果为

[{"Name":"A1","Age":10},{"Name":"A2","Age":11},{"Name":"B1","Age":10},{"Name":"B2","Age":11}]

其实SelectMany返回的是一个IEnumerable集合,是一个IEnumerable<Person>也就是说其实是把当前集合中的下一层的集合作为参数丢过去,然后返回回来一个合并的集合。
原始数据结构参考

[{
        "CompName": "公司1",
        "Emps": [{
                "Name": "A1",
                "Age": 10
            }, {
                "Name": "A2",
                "Age": 11
            }
        ]
    }, {
        "CompName": "公司2",
        "Emps": [{
                "Name": "B1",
                "Age": 10
            }, {
                "Name": "B2",
                "Age": 11
            }
        ]
    }
]

查看源码


F12

#region 程序集 System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.Core.dll
#endregion

namespace System.Linq
{
    //
    // 摘要:
    //     提供一组用于查询实现 System.Collections.Generic.IEnumerable`1 的对象的 static(在 Visual Basic
    //     中为 Shared)方法。
    public static class Enumerable

//
// 摘要:
//     将序列的每个元素投影到 System.Collections.Generic.IEnumerable`1 并将结果序列合并为一个序列。
//
// 参数:
//   source:
//     一个要投影的值序列。
//
//   selector:
//     应用于每个元素的转换函数。
//
// 类型参数:
//   TSource:
//     source 中的元素的类型。
//
//   TResult:
//     selector 返回的序列元素的类型。
//
// 返回结果:
//     一个 System.Collections.Generic.IEnumerable`1,其元素是对输入序列的每个元素调用一对多转换函数的结果。
//
// 异常:
//   T:System.ArgumentNullException:
//     source 或 selector 为 null。
public static IEnumerable<TResult> SelectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector);
/// <summary>Projects each element of a sequence to an <see cref="T:System.Collections.Generic.IEnumerable`1" /> and flattens the resulting sequences into one sequence.</summary>
/// <param name="source">A sequence of values to project.</param>
/// <param name="selector">A transform function to apply to each element.</param>
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
/// <typeparam name="TResult">The type of the elements of the sequence returned by <paramref name="selector" />.</typeparam>
/// <returns>An <see cref="T:System.Collections.Generic.IEnumerable`1" /> whose elements are the result of invoking the one-to-many transform function on each element of the input sequence.</returns>
/// <exception cref="T:System.ArgumentNullException">
///         <paramref name="source" /> or <paramref name="selector" /> is <see langword="null" />.</exception>
[__DynamicallyInvokable]
public static IEnumerable<TResult> SelectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (selector == null)
    {
        throw Error.ArgumentNull("selector");
    }
    return SelectManyIterator(source, selector);
}

private static IEnumerable<TResult> SelectManyIterator<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
{
    foreach (TSource item in source)
    {
        foreach (TResult item2 in selector(item))
        {
            yield return item2;
        }
    }
}

可以看到最终实现是通过yield关键字

yield 关键字


yield是C#为了简化遍历操作实现的语法糖,我们知道如果要要某个类型支持遍历就必须要实现系统接口IEnumerable,这个接口后续实现比较繁琐要写一大堆代码才能支持真正的遍历功能。举例说明

public static IEnumerable<string> GetLst()
{
    yield return "1";
    yield return "2";
    yield break;
    yield return "4";
}

foreach (string item in GetLst())
{
    Console.WriteLine(item);
}
// 1 
// 2

可参考学习 https://www.cnblogs.com/blueberryzzz/p/8678700.html


衷心感谢前辈们的文章。

posted @ 2022-02-17 10:20  嘿嘿嘿~  阅读(655)  评论(0编辑  收藏  举报