C# Linq中 Select && SelectMany 使用技巧

Select 和 SelectMany 是我们开发中对集合常用的两个扩展方法,今天我就用几个小例子并结合源码形式展示下这两个方法的使用形式。.

前文回顾

【温故知新】C# Linq中 Where使用技巧

Select 的基本使用

首先我创建一个Student的类,类的结构如下:

 1   public class Student
 2    {
 3        public int Id { get; set; }  // 学生ID
 4        public string Name { get; set; } // 学生姓名
 5        public List<string> Programing { get; set; } // 学生掌握的 编程语言
 6
 7
 8        // 获取学生集合
 9        public static List<Student> GetStudents()
10        {
11            return new List<Student>()
12            {
13                new Student
14                { 
15                    Id = 1, 
16                    Name ="张三",
17                    Programing = new List<string>
18                    {
19                        "C#","Java","JS"
20                    }
21                },
22                new Student
23                {
24                    Id=2,
25                    Name ="李四",
26                    Programing=new List<string>
27                    {
28                        "C++","C","Node.js"
29                    }
30                },
31                 new Student
32                {
33                    Id=3,
34                    Name ="王五",
35                    Programing=new List<string>
36                    {
37                        "Sql","C","Node.js"
38                    }
39                },
40                new Student
41                {
42                    Id=4,
43                    Name ="赵六",
44                    Programing=new List<string>
45                    {
46                        "MVC","C","Node.js"
47                    }
48                },
49            };
50        }
51
52    }

然后在Main函数中调用GetStudents方法,并对集合使用Select方法

1// 因为Id是int 类型 将集合里面所有子项的Id组合起来 变成一个新集合
2// 所以返回值是 IEnumerable<int> 类型
3IEnumerable<int> ids = Student.GetStudents().Select(s => s.Id);  
4foreach (var item in ids)
5{
6  Console.WriteLine(item); // 输出 1,2,3,4
7}

那么对于这个Select方法,內部源码是怎么实现的呢?

1// F12 进入查看源码结构
2// 返回值是 IEnumerable<TResult> 类型
3// 是所有 IEnumerable<TSource> source 类型的扩展方法
4// 一个入参 Func<TSource, TResult> selector 的内置委托 其中 委托入参是TSource 类型,返回值是TResult类型
5public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)

手写Select源码实现:

 1public static IEnumerable<TResult> SelectExtension<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
 2{
 3  if(source == null) throw new ArgumentNullException("source");
 4  if(selector == null) throw new ArgumentNullException("selector");
 5
 6  foreach (var item in source)
 7  {
 8    yield return selector(item);
 9  }
10}

SelectMany 的基本使用

1.在上文定义的Student类中,我们要拿到所有人会的编程语言,那么就可以通过SelectMany实现。

1var stus = Student.GetStudents().SelectMany(s => s.Programing);
2foreach (var item in stus)
3{
4   Console.WriteLine(item);
5}

C# Linq中 Select && SelectMany 使用技巧

那么这个方法中SelectMany的作用是什么呢?
SelectMany 将序列(也就是Student的集合)的每个元素投影到IEnumerable上,这样就将其转化为一个IEnumerable类型的数据。也就是说SelectMany 这个方法将一系列结果组合在一起变成一个新的结果。
以下便是SelectMany的一个重载源码实现:

 1// 返回值是 IEnumerable<TResult> 类型
 2// 是所有 IEnumerable<TSource> source 类型的扩展方法
 3// 一个入参 Func<TSource, IEnumerable<TResult>> selector 类型的内置委托 
 4// 其中委托函数需要一个Tsource类型的参数,返回值是IEnumerable<TResult>> 类型
 5public static IEnumerable<TResult> SelectManyExtension<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
 6{
 7  if(source == null) throw new ArgumentNullException("source");
 8  if(selector == null) throw new ArgumentNullException("selector");
 9   return SelectManyExtensionIterator(source, selector);
10}
11//SelectManyExtensionIterator 迭代实现
12static IEnumerable<TResult> SelectManyExtensionIterator<TSource, TResult>(IEnumerable<TSource> source,
13Func<TSource, IEnumerable<TResult>> selector)
14{
15   foreach (var item in source)
16    {
17      foreach (var result in selector(item))
18       {
19         yield return result;
20       }
21    }
22}

2.要拿到每个学生姓名及其所会的编程语言?这就可以通过SelectMany方法的另一种重载来实现

 1// 其中SelectMany 第一入参是一个内置委托:
 2  //入参为集合的元素类型,返回值是元素的一个集合类型(Student)
 3// 第二个参数也是一个内置委托: 
 4  //入参有两个,第一个为集合元素类型(Student),第二个为前面参数返回值的元素类型
 5  // 其中这个内置委托的返回值是一个TResult 类型,此处代码TResult 为new 出来的匿名类型
 6var stuInfo = Student.GetStudents().SelectMany(s => s.Programing,
 7                (stu, str) => new
 8                {
 9                    StudentName = stu.Name,
10                    PragramName = str
11                });
12foreach (var item in stuInfo)
13{
14  Console.WriteLine(item.StudentName+"=>"+item.PragramName);
15}

C# Linq中 Select && SelectMany 使用技巧

那么这个SelectMany的重载源代码是怎么实现的呢?

 1public static IEnumerable<TResult> SelectManyExtension2<TSource, TCollection, TResult>(this IEnumerable<TSource> source, 
 2            Func<TSource, IEnumerable<TCollection>> collectionSelector, 
 3            Func<TSource, TCollection, TResult> resultSelector)
 4        {
 5            if(source==null) throw new ArgumentNullException("source");
 6            if(collectionSelector == null) throw new ArgumentNullException("collectionSelector");
 7            if(resultSelector == null) throw new ArgumentNullException("resultSelector");
 8
 9            return SelectManyExtensionIterator2<TSource, TCollection, TResult>(source, collectionSelector, resultSelector);
10        }
11
12
13        static IEnumerable<TResult> SelectManyExtensionIterator2<TSource, TCollection, TResult> (IEnumerable<TSource> source,
14            Func<TSource, IEnumerable<TCollection>> collectionSelector,
15            Func<TSource, TCollection, TResult> resultSelector)
16        {
17            foreach (var item in source)
18            {
19                foreach (var collection in collectionSelector(item))
20                {
21                    yield return resultSelector(item, collection);
22                }
23            }
24        }

具体演示看上文视频~