C#Linq技术中SelectMany(...)函数的内部实现的伪代码

我们先来假设这种场景:

一个学校中有多个年级,一个年级有多个班级,一个班级里有多个学生。这里我们只需要班级、年级、和学生这三个概念;

让我们先来定义Class类和Student类:

 1    // 注意,Class是班级而不是 教室的意思,教室是 Classroom。
 2     public class Class
 3     {
 4         public int ClassId { get; set; }
 5         // 同一个班级的学生必然是属于同一个年级的,故GradeId直接在Class中声明就可以了。
 6         public int GradeId { get; set; }
 7         public List<Student> Students { get; set; }
 8     }
 9 
10     public class Student
11     {
12         public int StudentId { get; set; }
13         // 外键 ClassId
14         public int ClassId { get; set; }
       public string OtherProp{ get; set; }
15 // 注意,学生没有GradeId属性 16 }

现在来声明多个班级:

List<Class> classes = new List<Class>();
classes.AddRange(...);  // 这里添加的Class是有属于不同年级

现在来看SelectMany(...);函数的声明:

声明一:
public
static IEnumerable<TResult> SelectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector); 声明二: public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector);

对于第一个声明:我们调用该函数的形式即为:

classes.SelectMany(cls=>cls.Students);  // 这里很重要的一点,Func<TSource,IEnumerable<TResult>>里面的不是参数,而是类型,Func中最后一个泛型一定是返回值的泛型类型
// 这里classes 就是函数原型声明中的 source,而TSource就是Class类型,TResult就是 Student类型;而对于委托参数 selector中的 IEnumerable<TResult>实际上就是当前cls
// 代表的班级的所有学生,直接用cls.Students即可(而不是说自己声明一个 IEnumerable<Student> studs,然后将studs传到Func中,该Func只有一个参数)。

如上所示的代码实际上就是将多个班级classes中的所有学生都选出来返回一个 IEnumerable<Student> students,但是这里会存在一个问题,就是students中的学生

是classes中的学生总和,但是这时候我们无法知道students中某学生所属的年级是哪个了,如果想在返回classes中所有学生的集合的同时,对Student删减一些属性和

增加GradeId属性,这时候可以用第二个声明的SelectMany(..)函数,调用方法如下:

// 这里 cls就是声明二中的对应第一个委托Func的参数,TSource就是Class类型,cls.Students即是IEnumerable<TCollection>类型返回值,TCollection就是Student类型
// 而stud 就是cls.Students中的某一个元素,new{....}返回的类型是匿名类型,就是 TResult类型。
classes.SelectMany(cls=>cls.Students,(cls,stud)=>new{ ClassId=stud.ClassId,GradeId=cls.GradeId,StudentId=stud.StudentId}); // 这里的属性的增减 形式是随自己需要来改变的,不一定就是要这样

这时候返回的 IEnumerable<TResult> ches变量就是 对原来的Student进行了属性增减后的新类型(匿名类型'a)的集合,ches集合的元素个数和之前

classes.SelectMany(cls=>cls.Students)返回的元素个数是一样的。这时候要遍历ches 就不能用具体类型,只能用XXX(var itm in ches) 了。

现在来看看第二个声明的SelectMany(...)的内部实现的一种方式及其伪代码:

     // TResult对应 'a匿名类型;TSource对应Class类型;TCollection对应Student类型
        // source 对应classes
        public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source, 
            Func<TSource, IEnumerable<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)
        {
            IEnumerable<TResult> listResult = new IEnumerable<TResult>();
            // source 对应 classes
            foreach(var cls in source)
            {
                // TCollection对应Student,blockStudents对应 当前班级 cls中的所有学生集合
                // 给collectionSelector传 Lambda表达式时给出了该委托承载的 函数的具体声明 cls=>cls.Students。
                // Lambda表达式产生的 匿名函数的 参数类型,返回值类型一定是已知的,这里是Class和IEnumerable<Student>
                IEnumerable<TCollection> blockStudents = collectionSelector(cls);

                /* 如果是第一个声明,那么这时候就会开始执行 listResult.AddRange(blockStudents);此时Student即是TResult*/

                foreach(var stud in blockStudents)  // 注意,这是第二层 foreach
                {
                    // resultSelection(cls,stud) 返回的是 TResult,即匿名类型 'a 的实体对象,并添加到  listResult中
                    // resultSelection承载的Lambda表达式为:(cls,stud)=>new {ClassId.......GradeId...}  ;new出的
                    // 匿名对象即为 TResult 类型。
                    listResult.Add(resultSelection(cls, stud));
                }  // Second Foreach End
            }  // First Foreach End
            return listResult;
        }  // SelectMany(...) End
原文地址:https://www.cnblogs.com/silentdoer/p/4772925.html