Linq中SelectMany图文详解-Chinar

Chinar blog :www.chinar.xin

SelectMany中文教程
本文提供全流程,中文翻译

助力快速理解 SelectMany 的高级用法

为初学者节省宝贵的时间,避免采坑!

Chinar —— 心分享、心创新!

我们的初衷是将一种简单的生活方式带给世人

使有限时间 具备无限可能

Chinar 教程效果:

文章目录
1 Intro —— 简介
1 Overload 1 —— 重载 1
2 Overload 2 —— 重载 2
3 Overload 2 —— 重载 3
4 Overload 2 —— 重载 4
5 Project —— 全脚本文件
支持
May Be —— 开发者,总有一天要做的事!

全文高清图片,点击即可放大观看 (很多人竟然不知道)
1 Intro —— 简介

Linq函数关键字大全!( Chinar Blog )

SelectMany对于初学者来讲,是一个比较难理解的函数。其内部逻辑,有点绕。

在使用上,对初学者来讲,尤其是容易懵逼…

但它的用途极其广泛,且极大的节省代码、提高代码可读性。

避免大量的循环代码

这里我以 4个例子,说明SelectMany的多种用法。

请大家仔细、耐心的看完。

///
/// 第一层:老爹
///
public class One
{
public string Name; //名字
public int Age; //年龄
public List TwoList; //儿子

    public One(string name, int age, List<Two> twoList)
    {
        Name    = name;
        Age     = age;
        TwoList = twoList;
    }
}


/// <summary>
/// 第二层:儿子
/// </summary>
public class Two
{
    public string      Name;      //名字
    public int         Age;       //年龄
    public List<Three> ThreeList; //儿子


    public Two(string name, int age, List<Three> threeList)
    {
        Name      = name;
        Age       = age;
        ThreeList = threeList;
    }


    public override string ToString()
    {
        return $"{nameof(Name)}: {Name}, {nameof(Age)}: {Age}, {nameof(ThreeList)}: {ThreeList}";
    }
}


/// <summary>
/// 第三层:孙子
/// </summary>
public class Three
{
    public int Age;   //年龄
    public int Score; //分数


    public Three(int age, int score)
    {
        Age   = age;
        Score = score;
    }


    public override string ToString()
    {
        return $"{nameof(Age)}: {Age}, {nameof(Score)}: {Score}";
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
初始化,填入数据

为了便于大家理解,我用了
one、two、three 做为3层嵌套的子父类;
one 对应的数据值为: 爹、年龄
Two 对应:儿子 、 年龄

List ones = new List //第一层列表
{
new One(“爹1”, 1, new List //第儿子层
{
new Two(“儿子_0”, 20, new List {new Three(30, 100)}), //第三层
new Two(“儿子_1”, 21, new List {new Three(31, 100)}), //第三层
new Two(“儿子_2”, 22, new List {new Three(32, 100)}) //第三层
}),
new One(“爹2”, 10, new List
{
new Two(“儿子_0”, 23, new List {new Three(33, 70)})
}),
new One(“爹3”, 100, new List
{
new Two(“儿子_0”, 24, new List {new Three(34, 80)}),
new Two(“儿子_1”, 25, new List {new Three(35, 80)})
}),
new One(“爹4”, 1000, new List
{
new Two(“儿子_0”, 26, new List {new Three(36, 50)})
})
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1 Overload 1 —— 重载 1

我们来看下官方的代码注释:
//
// 摘要:
// 一个序列的每个元素投影 System.Collections.Generic.IEnumerable1 并将合并为一个序列将结果序列。 // // 参数: // source: // 一个要投影的值序列。 // // selector: // 应用于每个元素的转换函数。 // // 类型参数: // TSource: // 中的元素的类型 source。 // // TResult: // 返回的序列的元素的类型 selector。 // // 返回结果: // System.Collections.Generic.IEnumerable1 其元素是一种一对多转换函数对输入序列中的每个元素调用的结果。
//
// 异常:
// T:System.ArgumentNullException:
// source 或 selector 为 null。
public static IEnumerable SelectMany<TSource, TResult>(this IEnumerable source,
Func<TSource, IEnumerable> selector);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
懵逼不?这才是重载第一个
不要怕,有我,跟我来看个栗子

IEnumerable<List> onesSelect = ones.Select(one => one.TwoList); //Select 返回 List
IEnumerable onesSelectMany = ones.SelectMany(one => one.TwoList); //第一个重载:返回 Two
foreach (var lTwo in onesSelect)
{
Console.WriteLine(lTwo); //v=List
}

Console.WriteLine(“----------------------------”);
foreach (var two in onesSelectMany)
{
Console.WriteLine(two.ErName); //v=儿子的名字
}
1
2
3
4
5
6
7
8
9
10
11
12
输出结果

System.Collections.Generic.List1[SelectMany.Two] System.Collections.Generic.List1[SelectMany.Two]
System.Collections.Generic.List1[SelectMany.Two] System.Collections.Generic.List1[SelectMany.Two]

儿子_0
儿子_1
儿子_2
儿子_0
儿子_0
儿子_1
儿子_0
1
2
3
4
5
6
7
8
9
10
11
12

为了便于理解,我用A B C - 1 2 3对应了列表和对象的级别。

可以看出明显区别。

SelectMany 作用:

Ones列表中的 每个One 实例下都包含了一个TwoList列表

列表Ones 每个 One 对象: A1 B1 C1 D1 都包含了一个List

A2_0
A2_1
A2_2
B2_0
C2_0
C2_1
D2_0
1
2
3
4
5
6
7
将所有的List 重新组合成了一个新集合

就是语句的真谛
IEnumerable onesSelectMany = ones.SelectMany(one => one.TwoList); //第一个重载:返回 Two

返回类型为:IEnumerable的新集合

2 Overload 2 —— 重载 2

我们来看下第二个重载
public static IEnumerable SelectMany<TSource, TResult>(this IEnumerable source, Func<TSource,
int, IEnumerable> selector);
1
2
这个函数其实别第一个重载就多了一个 Int,这个是循环的下标

//这里的 index 指的是 List ones 对象的循环索引
IEnumerable stringEnumerable = ones.SelectMany((one, index) => new List { “索引:” + index + " 名字:" + one.Name });
foreach (var v in stringEnumerable)
{
Console.WriteLine(v); //v=String
}
1
2
3
4
5
6
输出结果

索引:0 名字:爹1
索引:1 名字:爹2
索引:2 名字:爹3
索引:3 名字:爹4
1
2
3
4
可以看出SelectMany 内部循环时,Index作为循环的下标 0-3

SelectMany 作用:

Ones列表中的 每个元素 One 的实例,

“索引:” + index + " 名字:" + one.Name"

=循环的 [索引 0-3+爹名字] 字符串拼接

将每个 字符串 ,重新组合成一个集合,并返回 所以类型是IEnumerable;

其内部函数运行原理基本与下方代码一致,只是返回类型为 IEnumerable

List stringList=new List();
for (int index = 0; index < ones.Count; index++)
{
stringList.Add(“索引:” + index + " 名字:" + ones[index].Name);
}
return stringList
1
2
3
4
5
6
3 Overload 2 —— 重载 3

我们来看下第3个重载
public static IEnumerable SelectMany<TSource, TCollection, TResult>(this IEnumerable source,
Func<TSource, IEnumerable> collectionSelector,
Func<TSource, TCollection, TResult> resultSelector);
1
2
3
更加有难度了啊

//返回匿名类型: <爹的名字,儿子的名字> 组成一个集合返回
var onesSelectMany3 = ones.SelectMany(one => one.TwoList, (one, two) => new {one.Name, two.ErName});
//可自定义返回类型
//var onesSelectMany3 = ones.SelectMany(one => one.TwoList, (one, two) => new Dictionary<string, int> {{ one.Name, two.Age } });
foreach (var v in onesSelectMany3)
{
Console.WriteLine(v); //v=匿名类型
}
1
2
3
4
5
6
7
8
输出结果

{ Name = 爹1, ErName = 儿子_0 }
{ Name = 爹1, ErName = 儿子_1 }
{ Name = 爹1, ErName = 儿子_2 }
{ Name = 爹2, ErName = 儿子_0 }
{ Name = 爹3, ErName = 儿子_0 }
{ Name = 爹3, ErName = 儿子_1 }
{ Name = 爹4, ErName = 儿子_0 }
1
2
3
4
5
6
7
SelectMany 重载3的作用:

第一个参数:one => one.TwoList
这个不难理解,就是我们重载1中提到过的。其返回的是IEnumerable 所有 儿子的大集合。

第二个参数:(one, two) => new {one.Name, two.ErName}
one和two分别指 映射后的 one和two实例
循环中,会执行每一项 父和子其顺序为:

  1. { Name = 爹1, ErName = 儿子_0 }
  2. { Name = 爹1, ErName = 儿子_1 }
  3. { Name = 爹1, ErName = 儿子_2 }
  4. { Name = 爹2, ErName = 儿子_0 }
  5. { Name = 爹3, ErName = 儿子_0 }
  6. { Name = 爹3, ErName = 儿子_1 }
  7. { Name = 爹4, ErName = 儿子_0 }

返回值为自定义匿名类型new {one.Name, two.ErName}
也就是 {父的名字,儿子的名字}组成的自定义类型,遍历每一项,并返回一个由匿名类型组成的大集合。

自定义类型根据需求自行变更,扩展性非常强健!

var onesSelectMany3 = ones.SelectMany(one => one.TwoList, (one, two) => new Dictionary<string, int> {{ one.Name, two.Age } });
1
4 Overload 2 —— 重载 4

我们来看下第4个重载
public static IEnumerable SelectMany<TSource, TCollection, TResult>(this IEnumerable source,
Func<TSource, IEnumerable> collectionSelector,
Func<TSource, TCollection, TResult> resultSelector);
1
2
3
只比第三个多了个Index下标,没什么难的。思维不要乱!

为了便于初学者理解,我们先写个简单的式子,看下每个儿子的年龄现在是多少?

var onesSelectMany5 = ones.SelectMany((one) => one.TwoList.Select(two => two.Age));
foreach (var v in onesSelectMany5)
{
Console.WriteLine(v); //v=int 年龄
}
1
2
3
4
5
输出结果

20
21
22
23
24
25
26
1
2
3
4
5
6
7
我们把儿子们的年龄,改为第四个重载函数的Index值
并返回自定义类型
//将每个儿子的年龄值改为当前 Index 索引值
//并返回匿名类型{爹,儿子年龄改为索引后的值}
var onesSelectMany4 = ones.SelectMany((one, index) => one.TwoList.Select(two => two.Age = index), (one, twoAge) => new {one.Name, twoAge}); //返回匿名类型: 1级的名字,2级的年龄
foreach (var v in onesSelectMany4)
{
Console.WriteLine(v); //v=匿名类型
}
1
2
3
4
5
6
7
输出结果

{ Name = 爹1, twoAge = 0 }
{ Name = 爹1, twoAge = 0 }
{ Name = 爹1, twoAge = 0 }
{ Name = 爹2, twoAge = 1 }
{ Name = 爹3, twoAge = 2 }
{ Name = 爹3, twoAge = 2 }
{ Name = 爹4, twoAge = 3 }
1
2
3
4
5
6
7

SelectMany 重载4的作用:

第一个参数:(one, index) => one.TwoList.Select(two => two.Age = index)
就是将每个Two中 儿子的年龄,改为Index的值,返回类型为 IEnumerable 儿子们年龄的大集合

第二个参数:(one, twoAge) => new {one.Name, twoAge}
one和 twoAge 分别指 映射后的 one和twoAge实例
返回值为自定义匿名类型new {one.Name, twoAge}
也就是 {父的名字,儿子的年龄(索引)}组成的自定义类型,遍历每一项,并返回一个由匿名类型组成的大集合。

学习更多Linq知识,请看我其他表达式的讲解;
Linq函数关键字大全!( Chinar Blog )

5 Project —— 全脚本文件

项目文件为 C# 控制台脚本文件

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace SelectMany
{
///
/// 第一层:老爹
///
public class One
{
public string Name; //名字
public int Age; //年龄
public List TwoList; //儿子

    public One(string name, int age, List<Two> twoList)
    {
        Name    = name;
        Age     = age;
        TwoList = twoList;
    }
}


/// <summary>
/// 第儿子层:儿子
/// </summary>
public class Two
{
    public string      ErName;    //名字
    public int         Age;       //年龄
    public List<Three> ThreeList; //儿子


    public Two(string name, int age, List<Three> threeList)
    {
        ErName    = name;
        Age       = age;
        ThreeList = threeList;
    }
}


/// <summary>
/// 第三层:孙子
/// </summary>
public class Three
{
    public int Age;   //年龄
    public int Score; //分数


    public Three(int age, int score)
    {
        Age   = age;
        Score = score;
    }
}


class Program
{
    static void Main(string[] args)
    {
        List<One> ones = new List<One> //第一层列表
        {
            new One("爹1", 1, new List<Two> //第儿子层
            {
                new Two("儿子_0", 20, new List<Three> {new Three(30, 100)}), //第三层
                new Two("儿子_1", 21, new List<Three> {new Three(31, 100)}), //第三层
                new Two("儿子_2", 22, new List<Three> {new Three(32, 100)})  //第三层
            }),
            new One("爹2", 10, new List<Two>
            {
                new Two("儿子_0", 23, new List<Three> {new Three(33, 70)})
            }),
            new One("爹3", 100, new List<Two>
            {
                new Two("儿子_0", 24, new List<Three> {new Three(34, 80)}),
                new Two("儿子_1", 25, new List<Three> {new Three(35, 80)})
            }),
            new One("爹4", 1000, new List<Two>
            {
                new Two("儿子_0", 26, new List<Three> {new Three(36, 50)})
            })
        };
        #region 重载1

        IEnumerable<List<Two>> onesSelect     = ones.Select(one => one.TwoList);     //Select 返回 List<Two>
        IEnumerable<Two>       onesSelectMany = ones.SelectMany(one => one.TwoList); //第一个重载:返回 Two
        foreach (var lTwo in onesSelect)
        {
            Console.WriteLine(lTwo); //v=List<Two>
        }

        Console.WriteLine("----------------------------");
        foreach (var two in onesSelectMany)
        {
            Console.WriteLine(two.ErName); //v=儿子的名字
        }

        #endregion
        #region 重载 2

        Console.WriteLine("----------重载 二 ------------");
        //这里的 index 指的是 List<One> ones 中对象的索引
        IEnumerable<string> stringEnumerable = ones.SelectMany((one, index) => new List<string> {"索引:" + index + "         名字:" + one.Name});
        foreach (var v in stringEnumerable)
        {
            Console.WriteLine(v); //v=String
        }

        #endregion
        #region 重载 3

        Console.WriteLine("----------重载 三 ------------");
        //返回匿名类型: <爹的名字,儿子的名字>  组成一个集合返回
        var onesSelectMany3 = ones.SelectMany(one => one.TwoList, (one, two) => new {one.Name, two.ErName});
        //可自定义返回类型
        //var onesSelectMany3 = ones.SelectMany(one => one.TwoList, (one, two) => new Dictionary<string, int> {{ one.Name, two.Age } });
        foreach (var v in onesSelectMany3)
        {
            Console.WriteLine(v); //v=匿名类型
        }

        #endregion
        #region 重载 4

        Console.WriteLine("----------重载 四 ------------");
        先看下儿子的年龄现在是多少?
        var onesSelectMany5 = ones.SelectMany((one) => one.TwoList.Select(two => two.Age));
        foreach (var v in onesSelectMany5)
        {
            Console.WriteLine(v); //v=int 年龄
        }

        //将每个儿子的年龄值改为当前 Index 索引值
        //并返回匿名类型{爹,儿子年龄改为索引后的值}
        var onesSelectMany4 = ones.SelectMany((one, index) => one.TwoList.Select(two => two.Age = index), (one, twoAge) => new {one.Name, twoAge}); //返回匿名类型: 1级的名字,2级的年龄
        foreach (var v in onesSelectMany4)
        {
            Console.WriteLine(v); //v=匿名类型
        }

        #endregion
        Console.ReadLine();
    }


    /// <summary>
    /// 获取对象内存地址
    /// </summary>
    /// <param name="obj">对象</param>
    /// <returns>内存地址</returns>
    public static string GetMemoryAddress(object obj)
    {
        return GCHandle.Alloc(obj, GCHandleType.Pinned).AddrOfPinnedObject().ToString("X");
    }
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
支持
May Be —— 开发者,总有一天要做的事!

拥有自己的服务器,无需再找攻略

Chinar 提供一站式《零》基础教程

使有限时间 具备无限可能!
先点击领取 —— 阿里全产品优惠券 (享受最低优惠)

Chinar 免费服务器、建站教程全攻略!( Chinar Blog )

END
本博客为非营利性个人原创,除部分有明确署名的作品外,所刊登的所有作品的著作权均为本人所拥有,本人保留所有法定权利。违者必究

对于需要复制、转载、链接和传播博客文章或内容的,请及时和本博主进行联系,留言,Email: ichinar@icloud.com

对于经本博主明确授权和许可使用文章及内容的,使用时请注明文章或内容出处并注明网址
————————————————
版权声明:本文为CSDN博主「Chinarcsdn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ChinarCSDN/article/details/105648952

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值