概要

在开发过程中,LINQ的ToList()方法经常被使用,帮助我们将将迭代器转换为具体的List对象。为了更好的了解该方法的工作原理,我们从源码的角度对其进行分析。

ToList方法介绍

ToList作为IEnumerable的扩展方法,可以帮助我们将IEnumerable转换为List。

ToList源码介绍

源码GIT地址:

 public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source){if (source == null){ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);}return source is IIListProvider<TSource> listProvider ? listProvider.ToList() : new List<TSource>(source);}

ToList本身代码很简单,就是判断参数source是否实现了IIListProvider接口,如果实现了该接口,则调用该方法进行转换,如果未实现该接口,则直接调用List的构造方法,创建一个新的List,然后返回。

IIListProvider接口

 internal interface IIListProvider<TElement> : IEnumerable<TElement>{TElement[] ToArray();List<TElement> ToList();       int GetCount(bool onlyIfCheap);}

IIListProvider接口定义了三个方法,其中包括ToList方法。

ToList优化分析

对于一个常见的LINQ操作xx.Where().Select().ToList(), 假设xx是一个IEnumerable, 我们希望可以一次遍历xx,实现过滤,投影和转List的操作。不希望在完成Select操作后,重新遍历xx进行List的转化。

在LINQ的源码中,我们可以找到许多包含Opt的文件,例如Select.SpeedOpt.cs,SelectMany.SpeedOpt.cs等等。在这些文件中,实现了IIListProvider的ToList接口,从而保证将ToList操作合并到前面的操作中,避免多次遍历数据源。

下面我们就分析一下,如何将ToList操作和ToList前面的方法进行合并。

我们使用如下数据进行分析:

List<Student> studentList = new List<Student>()
{ new Student("x001", "Tom", "CN-1" , 90),new Student("x002", "Jack", "CN-1", 88),new Student("x003", "Mary", "CN-2", 87),new Student("x004", "Frank", "CN-2", 97),};

Student和Teacher类代码见附录。

Select().Tolist()

var list = studentList.Select(s => new {Name= s.Name, Math = s.MathResult}).ToList();

对于Select().ToList(),它的执行流程如下:

  1. 进入扩展方法Select,studentList是List类型,所以返回SelectListIterator迭代器对象,该对象包含投影方法和lList数据。
  2. 进入扩展方法ToList,SelectListIterator实现了IIListProvider方法,所以实际上是调用SelectListIterator自己的ToList方法,该方法源码如下:
private sealed partial class SelectListIterator<TSource, TResult> : IPartition<TResult>{public List<TResult> ToList(){int count = _source.Count;var results = new List<TResult>(count);for (int i = 0; i < count; i++){results.Add(_selector(_source[i]));}return results;}
}
  1. IPartition是一个用于处理分页的接口,该接口继承了 IIListProvider,因此SelectListIterator类需实现方法ToList;
  2. 进入该方法后,获取studentList内元素个数,因为List实现了ICollection接口,所以通过属性值Count直接获取序列内元素个数;
  3. 定义新的List序列。
  4. 在序列每个元素上执行投影操作,将结果存入新建的List中;
  5. 返回List对象。

Where().ToList()

var list = studentList.Where(s => s.MathResult >= 90).ToList();

对于Where().Tolist(),它的执行流程如下:

  1. 进入扩展方法Where,studentList是List类型,所以返回WheretListIterator迭代器对象,该对象包含过滤方法和List数据。
  2. 进入扩展方法ToList,WheretListIterator实现了IIListProvider方法,所以实际上是调用WheretListIterator自己的ToList方法,该方法源码如下:
 private sealed partial class WhereListIterator<TSource> : Iterator<TSource>, IIListProvider<TSource>{public List<TSource> ToList(){var list = new List<TSource>();for (int i = 0; i < _source.Count; i++){TSource item = _source[i];if (_predicate(item)){list.Add(item);}}return list;}}
  1. 进入ToList方法后, 定义新的List序列。
  2. 在序列每个元素上执行过滤操作,将predict返回为true的元素存入List中;
  3. 返回List对象。

Where().Select().ToList()

对于Where().Select().Tolist(),它的执行流程如下:

var list = studentList.Where(s=>s.MathResult >= 90).Select(s => new {Name= s.Name, Math = s.MathResult}).ToList();
  1. 进入扩展方法Where,studentList是List类型,所以返回WheretListIterator迭代器对象,该对象包含过滤方法和List数据。
  2. 进入扩展方法Select, WheretListIterator迭代器对象是Iterator基类的派生类,所以在Select方法中,调用的是WheretListIterator迭代器对象的Select()方法,返回WhereSelectListIterator迭代器对象,该对象包含传入的List数据,过滤方法和投影方法。
  3. 进入扩展方法ToList,WhereSelectListIterator同样实现了IIListProvider接口,因此直接调用该方法,代码如下:
 private sealed partial class WhereSelectListIterator<TSource, TResult> : IIListProvider<TResult>{public List<TResult> ToList(){var list = new List<TResult>();for (int i = 0; i < _source.Count; i++){TSource item = _source[i];if (_predicate(item)){list.Add(_selector(item));}}return list;}}
  1. 进入ToList方法后, 定义新的List序列。
  2. 在序列每个元素上执行过滤操作,对predict返回为true的元素执行投影操作,将结果存入List中;
  3. 返回List对象。

SelectMany().ToList()

List<Student> studentList1 = new List<Student>(){ new Student("x005", "Henry", "CN-1" , 90),new Student("x006", "Lance", "CN-1", 88),new Student("x007", "Steven", "CN-2", 87),new Student("x008", "Carl", "CN-2", 97),};Teacher teacher1 = new Teacher{Id = "t001",Name  = "Jane",Students = studentList};Teacher teacher2 = new Teacher{Id = "t002",Name  = "David",Students = studentList1};List<Teacher> teachers = new List<Teacher>{teacher1,teacher2};var students = teachers.SelectMany2(t => t.Students).ToList();

对于SelectMany().ToList(),它的执行流程如下:

  1. 进入扩展方法SelectMany,返回SelectManySingleSelectorIterator迭代器对象;
  2. 进入扩展方法ToList(),SelectManySingleSelectorIterator迭代器实现了IIListProvider接口,所以直接调用该对象的ToList()方法,该方法源码如下:
private sealed partial class SelectManySingleSelectorIterator<TSource, TResult> : IIListProvider<TResult>{public List<TResult> ToList(){var list = new List<TResult>();foreach (TSource element in _source){list.AddRange(_selector(element));}return list;}
}
  1. 进入ToList方法后, 定义新的List序列对象;
  2. 遍历SelectManySingleSelectorIterator中的数据_source, 将每个元素投影成一个IEnumerable序列,并附加到新的List序列对象中;
  3. 返回List对象。

Distinct().ToList()

public class StudentEqualityComparer : IEqualityComparer<Student>{public bool Equals(Student b1, Student b2) { return b1.Id.Equals(b2.Id);}   public int GetHashCode(Student bx) =>  bx.Id.GetHashCode();        }List<Student> studentList1 = new List<Student>(){ new Student("x005", "Henry", "CN-1" , 90),new Student("x006", "Lance", "CN-1", 88),new Student("x007", "Steven", "CN-2", 87),new Student("x007", "Carl", "CN-2", 97),};var stuList =  studentList.Distinct2(new StudentEqualityComparer());

对于Distinct().ToList(),它的执行流程如下:

  1. 进入扩展方法Distinct,返回DistinctIterator迭代器对象,该对象包含List数据和自定义比较器StudentEqualityComparer对象;
  2. 进入扩展方法ToList(),DistinctIterator类也实现了IIListProvider接口,所以直接调用该对象的ToList()方法,该方法源码如下:
private sealed partial class DistinctIterator<TSource> : IIListProvider<TSource>{public List<TSource> ToList() => Enumerable.HashSetToList(new HashSet<TSource>(_source, _comparer));
}
  1. 进入ToList方法后,将源序列数据作为参数实例化成一个HashSet,并将比较器对象传入,将重复元素进行过滤。
  2. 将HashSet作为参数,直接调用Enumerable的静态方法HashSetToList,将HashSet对象转为List对象;
  3. 返回List对象。

附录

public class Student {public string Id { get; set; }public string Name { get; set; }public string Classroom { get; set; }public int MathResult { get; set; }
}
public class Teacher{public string Id { get; set; }public string Name { get; set; }public List<Student> Students { get; set; }}

C# LINQ源码分析之ToList()相关推荐

  1. asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证

    asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证 原文:asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型 ...

  2. 深入理解Spark 2.1 Core (十):Shuffle Map 端的原理与源码分析

    在上一篇<深入理解Spark 2.1 Core (九):迭代计算和Shuffle的原理与源码分析>提到经过迭代计算后, SortShuffleWriter.write中: // 根据排序方 ...

  3. 深入理解Spark 2.1 Core (六):Standalone模式运行的原理与源码分析

    我们讲到了如何启动Master和Worker,还讲到了如何回收资源.但是,我们没有将AppClient是如何启动的,其实它们的启动也涉及到了资源是如何调度的.这篇博文,我们就来讲一下AppClient ...

  4. 深入理解Spark 2.1 Core (五):Standalone模式运行的原理与源码分析

    概述 前几篇博文都在介绍Spark的调度,这篇博文我们从更加宏观的调度看Spark,讲讲Spark的部署模式.Spark部署模式分以下几种: local 模式 local-cluster 模式 Sta ...

  5. 深入理解Spark 2.1 Core (二):DAG调度器的原理与源码分析

    概述 上一篇<深入理解Spark(一):RDD实现及源码分析 >提到: 定义RDD之后,程序员就可以在动作(注:即action操作)中使用RDD了.动作是向应用程序返回值,或向存储系统导出 ...

  6. spark mllib源码分析之随机森林(Random Forest)

    Spark在mllib中实现了tree相关的算法,决策树DT(DecisionTree),随机森林RF(RandomForest),GBDT(Gradient Boosting Decision Tr ...

  7. [Abp vNext 源码分析] - 2. 模块系统的变化

    一.简要说明 本篇文章主要分析 Abp vNext 当中的模块系统,从类型构造层面上来看,Abp vNext 当中不再只是单纯的通过 AbpModuleManager 来管理其他的模块,它现在则是 I ...

  8. 【转】Spark源码分析之-scheduler模块

    原文地址:http://jerryshao.me/architecture/2013/04/21/Spark%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E4%B9%8B- ...

  9. NEO从源码分析看共识协议

    2019独角兽企业重金招聘Python工程师标准>>> 0x00 概论 不同于比特币使用的工作量证明(PoW)来实现共识,NEO提出了DBFT共识算法.DBFT改良自股权证明算法(P ...

最新文章

  1. cmd 文本替换_将CMD信息保存为文件
  2. python绘图内容怎么保存_将绘图保存到图像文件,而不是使用Matplotlib显示 - python...
  3. 哈,又一款超级简单的队列(MQ)实现方案来了~
  4. mysql 存储 事务_MYSQL 可以在存储过程里实现事务控制吗
  5. 三角形描边css,[CSS] tips带有描边的小箭头
  6. 字符串转内存c语言,【一起学C】C语言面试题必考:字符串操作函数,内存操作函数实现...
  7. Qt之QPropertyAnimation
  8. gerrit与crowdid, openid集成,设置openIdSsoUrl 直接登录
  9. 今天需要修复的bug
  10. 19-基础教育知识图谱赋能智慧教育
  11. 复读复旦大学计算机考研,2021车辆跨考计算机408,407分上岸复旦计算机,弯路预警!!!...
  12. word图片自动生成域
  13. Java集合(一)什么是集合
  14. win7默认网关不可用怎么解决
  15. Android中添加商品的购物车
  16. 分享下Python从业者的生存现状,告诉你一般程序员真实工资
  17. 主存/内存/外存 区分
  18. 柱状图标签在柱的上方怎么进行展示
  19. 红米android10参数,红米note9详细参数表_红米note9参数配置详情
  20. H5实现无插件视频监控按需直播

热门文章

  1. excel中文显示乱码
  2. 解决docker容器映射信息修改问题
  3. 拥抱 Android Studio 之四:Maven 仓库使用与私有仓库搭建
  4. jquery 常用选择器和方法以及遍历(超详细)
  5. Linux 未定义的引用解决记录
  6. 网易2018校园招聘:魔法币 [python]
  7. Oracle定时任务dbms_scheduler
  8. 2021Java面经:最便宜java培训机构
  9. 中国星际争霸历史回顾(重写版)
  10. 通过L0phtcrack 7进行账号口令破解