C# Linq源码分析之Take方法

概要

Take方法作为IEnumerable的扩展方法,具体对应两个重载方法。本文主要分析第一个接收整数参数的重载方法。

源码解析

Take方法的基本定义

public static System.Collections.Generic.IEnumerable Take (this System.Collections.Generic.IEnumerable source, int count);

基本功能是从序列source中,返回指定个数count的相邻元素。

源码分析

Take.cs

csharp 复制代码
public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
{
   if (source == null)
   {
     ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
   }

   return count <= 0 ?
       Empty<TSource>() :
       TakeIterator<TSource>(source, count);
}

Take方法本身代码很简单, 首先作了一个空序列的检查,如果序列为空,则抛出异常。然后如果count是0,即取前0项相邻元素,等价于什么也不作,直接返回,否则调用TakeIterator方法。

Take.SizeOpt.cs

csharp 复制代码
private static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
    Debug.Assert(count > 0);

    foreach (TSource element in source)
    {
        yield return element;
        if (--count == 0) break;
    }
}

TakeIterator方法并没有像我们之前分析的Where,Select等方法那样,根据功能,定于很多Iterator的派生类来实现具体的功能,而是使用了yield return的方式。

按照count的个数取出对应的元素,以yield return的方式返回。

下面我们使用相同的代码,定义我们自己的扩展方法take 和takeIterator,通过log来搞清楚yield return方式的实现细节。

csharp 复制代码
 public static IEnumerable<TSource> take<TSource>(this IEnumerable<TSource> source, int count)
{
    Console.WriteLine("take is called !");
    if (source == null)
    {             ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
    }
    return count <= 0 ?
        Empty<TSource>() :
        takeIterator<TSource>(source, count);
}

private static IEnumerable<TSource> takeIterator<TSource>(IEnumerable<TSource> source, int count)
{
    Console.WriteLine("TakeIterator is called !");
    Debug.Assert(count > 0);

    foreach (TSource element in source)
    {
        Console.WriteLine("Enter takeIterator Foreach");
        yield return element;
        Console.WriteLine("Return " + element + " from takeIterator Foreach");
        if (--count == 0) break;
    }
}

Case 1 不通过toList或foreach循环来调用take的返回值。

csharp 复制代码
  static void Main(string[] args)
  {
   	 var list = Enumerable.Range(1,10).take(2);
  }

执行结果如下:

我们可以看到takeIterator并未被调用。

Case 2: 通过foreach循环来调用take的返回值

csharp 复制代码
static void Main(string[] args)
  {
   	 var list = Enumerable.Range(1,10).take(2);
   	 foreach (var item in list)
      {
          Console.WriteLine("Enter foreach Main function's  foreach");
          Console.WriteLine("Print " + item + " in Main function");
      }
  }

执行结果如下:

从执行结果可以看出:

  1. takeIterator函数只执行一次,但是会生成一个状态机,用于返回take出来的所有数据;
  2. Main函数中的foreach每次的取值,是从状态机中获取数据,即通过yield return的方式获取。

结论

通过定义具体迭代器实现的延迟加载和通过yield return方式实现的延迟加载,本质上没有区别。

但是实现上略有不同,定义迭代器方式实现的Where或Select等方法,如果没有取值操作,它只是将迭代器对象返回,迭代器对象中保存了迭代方式和源数据序列,对应的方法会被调用。通过yield return方式实现的迭代器,如果没有取值操作,yield return所在的方法不会被调用。

相关推荐
jieyucx29 分钟前
Go语言深度解剖:Map扩容机制全解析(增量扩容+等量扩容+渐进式迁移)
开发语言·后端·golang·map·扩容策略
顾温29 分钟前
default——C#/C++
java·c++·c#
InCerry1 小时前
.NET性能优化:提升Apache Arrow读写性能
c#·.net周刊
脏脏a1 小时前
【C++模版】泛型编程:代码复用的终极利器
开发语言·c++·c++模版
island13141 小时前
【C++仿Muduo库#3】Server 服务器模块实现上
服务器·开发语言·c++
散峰而望1 小时前
【算法竞赛】C/C++ 的输入输出你真的玩会了吗?
c语言·开发语言·数据结构·c++·算法·github
小龙报1 小时前
【C语言】内存里的 “数字变形记”:整数三码、大小端与浮点数存储真相
c语言·开发语言·c++·创业创新·学习方法·visual studio
深耕AI1 小时前
【VS Code避坑指南】点击Python图标提示“没有Python环境”,选择安装uv后这堆输出到底是什么意思?
开发语言·python·uv
2301_789015621 小时前
C++:继承
c语言·开发语言·c++
程序员威哥1 小时前
实战!Python爬京东商品评论:从采集到情感分析+词云可视化,新手30分钟跑通
开发语言·爬虫·python·scrapy