C# LINQ源码分析之ToList()

27 篇文章 1 订阅
23 篇文章 1 订阅

概要

在开发过程中,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; }
 }

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值