C# Linq源码分析之Take(五)

news2024/11/27 12:03:18

概要

本文在C# Linq源码分析之Take(四)的基础上继续从源码角度分析Take的优化方法,主要分析Where.Select.Take的使用案例。

Where.Select.Take的案例分析

该场景模拟我们显示中将EF中与数据库关联的对象进行过滤,然后转换成Web前端需要的对象,并分页的情况。

 studentList.Where(x => x.MathResult >= 90).Select(x => new {
                x.Name,
                x.MathResult
            }).Take(3).ToList().ForEach(x=>Console.WriteLine(x.Name + x.MathResult));

找到数学90分以上的学生,获取学生的姓名和数学成绩,每次只取前三个学生。并将学生信息打印。Student类的代码请见附录。

源码流程分析

在这里插入图片描述
第一步进入Where方法,返回WhereListIterator对象;
第二步进入Select方法,将Where和Select两个操作合并,返回WhereSelectListIterator对象;
第三步进入Take方法,调用takeIterator方法;由于人WhereSelectListIterator并没有实现IPartition接口和IList接口,所以无法再进行操作合并,只能返回EnumerablePartition对象。

private static IEnumerable<TSource> takeIterator<TSource>(IEnumerable<TSource> source, int count)
 {
     Debug.Assert(count > 0);
     return
        source is IPartition<TSource> partition ? partition.Take(count) :
        source is IList<TSource> sourceList ? new ListPartition<TSource>(sourceList, 0, count - 1) :
        new EnumerablePartition<TSource>(source, 0, count - 1);
 }

第四步进入ToList方法

  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);
 }

此时的source是EnumerablePartition对象,它实现了IPartition接口,而IPartition接口继承了IIListProvider接口,所以可以调用自己的ToList方法;

public List<TSource> ToList()
{
    var list = new List<TSource>();

    using (IEnumerator<TSource> en = _source.GetEnumerator())
    {
        if (SkipBeforeFirst(en) && en.MoveNext())
        {
            int remaining = Limit - 1; // Max number of items left, not counting the current element.
            int comparand = HasLimit ? 0 : int.MinValue; // If we don't have an upper bound, have the comparison always return true.

            do
            {
                remaining--;
                list.Add(en.Current);
            }
            while (remaining >= comparand && en.MoveNext());
        }
    }

    return list;
}
  1. 定义迭代器en,此时的_source是WhereSelectListIterator对象;
  2. 该ToList方法同样支持Skip,所以要判断迭代器的起始位置是不是从第一个开始;
  3. 每次迭代,首先从WhereSelectListIterator迭代器中返回一个符合过滤条件,并完成Selector操作的元素,存入list,直到list中包含三个元素,返回执行结果。

虽然WhereSelectListIterator没有实现IPartition接口,不能实现一次迭代,完成全部操作,但是现有的流程性能并不差,因为WhereSelectListIterator迭代器本身已经合并了过滤和投影操作,而且并不需要遍历所有元素,只要找到3个符合条件的元素即可。

我认为如果代码需要用到Take方法,尽量把它放到Linq的最后。这样做的好处是前面的Linq操作并不需要遍历全部的序列元素,只要得到Take方法中需要的元素个数即可。

本文中涉及的源码请见附录,关于WhereSelectListIterator的合并优化操作,更多详细内容,请参考C# LINQ源码分析之Select

附录

Student类

public class Student {
    public string Id { get; set; }
    public string Name { get; set; }
    public string Classroom { get; set; }
    public int MathResult { get; set; }
}

IIListProvider接口

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

IPartition接口

internal interface IPartition<TElement> : IIListProvider<TElement>
{

    IPartition<TElement> Skip(int count);

    IPartition<TElement> Take(int count);

    TElement? TryGetElementAt(int index, out bool found);

    TElement? TryGetFirst(out bool found);

    TElement? TryGetLast(out bool found);
}

EnumerablePartition类

private sealed class EnumerablePartition<TSource> : Iterator<TSource>, IPartition<TSource>
        {
            private readonly IEnumerable<TSource> _source;
            private readonly int _minIndexInclusive;
            private readonly int _maxIndexInclusive; // -1 if we want everything past _minIndexInclusive.
                                                     // If this is -1, it's impossible to set a limit on the count.
            private IEnumerator<TSource>? _enumerator;

            internal EnumerablePartition(IEnumerable<TSource> source, int minIndexInclusive, int maxIndexInclusive)
            {
                Debug.Assert(source != null);
                Debug.Assert(!(source is IList<TSource>), $"The caller needs to check for {nameof(IList<TSource>)}.");
                Debug.Assert(minIndexInclusive >= 0);
                Debug.Assert(maxIndexInclusive >= -1);
                // Note that although maxIndexInclusive can't grow, it can still be int.MaxValue.
                // We support partitioning enumerables with > 2B elements. For example, e.Skip(1).Take(int.MaxValue) should work.
                // But if it is int.MaxValue, then minIndexInclusive must != 0. Otherwise, our count may overflow.
                Debug.Assert(maxIndexInclusive == -1 || (maxIndexInclusive - minIndexInclusive < int.MaxValue), $"{nameof(Limit)} will overflow!");
                Debug.Assert(maxIndexInclusive == -1 || minIndexInclusive <= maxIndexInclusive);

                _source = source;
                _minIndexInclusive = minIndexInclusive;
                _maxIndexInclusive = maxIndexInclusive;
            }

            // If this is true (e.g. at least one Take call was made), then we have an upper bound
            // on how many elements we can have.
            private bool HasLimit => _maxIndexInclusive != -1;

            private int Limit => unchecked((_maxIndexInclusive + 1) - _minIndexInclusive); // This is that upper bound.

            public override Iterator<TSource> Clone() =>
                new EnumerablePartition<TSource>(_source, _minIndexInclusive, _maxIndexInclusive);

            public override void Dispose()
            {
                if (_enumerator != null)
                {
                    _enumerator.Dispose();
                    _enumerator = null;
                }

                base.Dispose();
            }

            public int GetCount(bool onlyIfCheap)
            {
                if (onlyIfCheap)
                {
                    return -1;
                }

                if (!HasLimit)
                {
                    // If HasLimit is false, we contain everything past _minIndexInclusive.
                    // Therefore, we have to iterate the whole enumerable.
                    //return Math.Max(_source.Count()- _minIndexInclusive, 0);
                    return 0;
                }

                using (IEnumerator<TSource> en = _source.GetEnumerator())
                {
                    // We only want to iterate up to _maxIndexInclusive + 1.
                    // Past that, we know the enumerable will be able to fit this partition,
                    // so the count will just be _maxIndexInclusive + 1 - _minIndexInclusive.

                    // Note that it is possible for _maxIndexInclusive to be int.MaxValue here,
                    // so + 1 may result in signed integer overflow. We need to handle this.
                    // At the same time, however, we are guaranteed that our max count can fit
                    // in an int because if that is true, then _minIndexInclusive must > 0.

                    uint count = SkipAndCount((uint)_maxIndexInclusive + 1, en);
                    Debug.Assert(count != (uint)int.MaxValue + 1 || _minIndexInclusive > 0, "Our return value will be incorrect.");
                    return Math.Max((int)count - _minIndexInclusive, 0);
                }

            }

            public override bool MoveNext()
            {
                // Cases where GetEnumerator has not been called or Dispose has already
                // been called need to be handled explicitly, due to the default: clause.
                int taken = _state - 3;
                if (taken < -2)
                {
                    Dispose();
                    return false;
                }

                switch (_state)
                {
                    case 1:
                        _enumerator = _source.GetEnumerator();
                        _state = 2;
                        goto case 2;
                    case 2:
                        Debug.Assert(_enumerator != null);
                        if (!SkipBeforeFirst(_enumerator))
                        {
                            // Reached the end before we finished skipping.
                            break;
                        }

                        _state = 3;
                        goto default;
                    default:
                        Debug.Assert(_enumerator != null);
                        if ((!HasLimit || taken < Limit) && _enumerator.MoveNext())
                        {
                            if (HasLimit)
                            {
                                // If we are taking an unknown number of elements, it's important not to increment _state.
                                // _state - 3 may eventually end up overflowing & we'll hit the Dispose branch even though
                                // we haven't finished enumerating.
                                _state++;
                            }
                            _current = _enumerator.Current;
                            return true;
                        }

                        break;
                }

                Dispose();
                return false;
            }

            public override IEnumerable<TResult> Select<TResult>(Func<TSource, TResult> selector) =>
                new SelectIPartitionIterator<TSource, TResult>(this, selector);

            public IPartition<TSource> Skip(int count)
            {
                int minIndex = unchecked(_minIndexInclusive + count);

                if (!HasLimit)
                {
                    if (minIndex < 0)
                    {
                        // If we don't know our max count and minIndex can no longer fit in a positive int,
                        // then we will need to wrap ourselves in another iterator.
                        // This can happen, for example, during e.Skip(int.MaxValue).Skip(int.MaxValue).
                        return new EnumerablePartition<TSource>(this, count, -1);
                    }
                }
                else if ((uint)minIndex > (uint)_maxIndexInclusive)
                {
                    // If minIndex overflows and we have an upper bound, we will go down this branch.
                    // We know our upper bound must be smaller than minIndex, since our upper bound fits in an int.
                    // This branch should not be taken if we don't have a bound.
                    return EmptyPartition<TSource>.Instance;
                }

                Debug.Assert(minIndex >= 0, $"We should have taken care of all cases when {nameof(minIndex)} overflows.");
                return new EnumerablePartition<TSource>(_source, minIndex, _maxIndexInclusive);
            }

            public IPartition<TSource> Take(int count)
            {
                int maxIndex = unchecked(_minIndexInclusive + count - 1);
                if (!HasLimit)
                {
                    if (maxIndex < 0)
                    {
                        // If we don't know our max count and maxIndex can no longer fit in a positive int,
                        // then we will need to wrap ourselves in another iterator.
                        // Note that although maxIndex may be too large, the difference between it and
                        // _minIndexInclusive (which is count - 1) must fit in an int.
                        // Example: e.Skip(50).Take(int.MaxValue).

                        return new EnumerablePartition<TSource>(this, 0, count - 1);
                    }
                }
                else if (unchecked((uint)maxIndex >= (uint)_maxIndexInclusive))
                {
                    // If we don't know our max count, we can't go down this branch.
                    // It's always possible for us to contain more than count items, as the rest
                    // of the enumerable past _minIndexInclusive can be arbitrarily long.
                    return this;
                }

                Debug.Assert(maxIndex >= 0, $"We should have taken care of all cases when {nameof(maxIndex)} overflows.");
                return new EnumerablePartition<TSource>(_source, _minIndexInclusive, maxIndex);
            }

            public TSource? TryGetElementAt(int index, out bool found)
            {
                // If the index is negative or >= our max count, return early.
                if (index >= 0 && (!HasLimit || index < Limit))
                {
                    using (IEnumerator<TSource> en = _source.GetEnumerator())
                    {
                        Debug.Assert(_minIndexInclusive + index >= 0, $"Adding {nameof(index)} caused {nameof(_minIndexInclusive)} to overflow.");

                        if (SkipBefore(_minIndexInclusive + index, en) && en.MoveNext())
                        {
                            found = true;
                            return en.Current;
                        }
                    }
                }

                found = false;
                return default;
            }

            public TSource? TryGetFirst(out bool found)
            {
                using (IEnumerator<TSource> en = _source.GetEnumerator())
                {
                    if (SkipBeforeFirst(en) && en.MoveNext())
                    {
                        found = true;
                        return en.Current;
                    }
                }

                found = false;
                return default;
            }

            public TSource? TryGetLast(out bool found)
            {
                using (IEnumerator<TSource> en = _source.GetEnumerator())
                {
                    if (SkipBeforeFirst(en) && en.MoveNext())
                    {
                        int remaining = Limit - 1; // Max number of items left, not counting the current element.
                        int comparand = HasLimit ? 0 : int.MinValue; // If we don't have an upper bound, have the comparison always return true.
                        TSource result;

                        do
                        {
                            remaining--;
                            result = en.Current;
                        }
                        while (remaining >= comparand && en.MoveNext());

                        found = true;
                        return result;
                    }
                }

                found = false;
                return default;
            }

   

            public List<TSource> ToList()
            {
                var list = new List<TSource>();

                using (IEnumerator<TSource> en = _source.GetEnumerator())
                {
                    if (SkipBeforeFirst(en) && en.MoveNext())
                    {
                        int remaining = Limit - 1; // Max number of items left, not counting the current element.
                        int comparand = HasLimit ? 0 : int.MinValue; // If we don't have an upper bound, have the comparison always return true.

                        do
                        {
                            remaining--;
                            list.Add(en.Current);
                        }
                        while (remaining >= comparand && en.MoveNext());
                    }
                }

                return list;
            }

            private bool SkipBeforeFirst(IEnumerator<TSource> en) => SkipBefore(_minIndexInclusive, en);

            private static bool SkipBefore(int index, IEnumerator<TSource> en) => SkipAndCount(index, en) == index;

            private static int SkipAndCount(int index, IEnumerator<TSource> en)
            {
                Debug.Assert(index >= 0);
                return (int)SkipAndCount((uint)index, en);
            }

            private static uint SkipAndCount(uint index, IEnumerator<TSource> en)
            {
                Debug.Assert(en != null);

                for (uint i = 0; i < index; i++)
                {
                    if (!en.MoveNext())
                    {
                        return i;
                    }
                }

                return index;
            }

            public TSource[] ToArray()
            {
                throw new NotImplementedException();
            }
        }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/979622.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝牙 or 2.4G or 5.8G?你会选择耳机吗

生活在网络时代&#xff0c;蓝牙、WIFI 已经是生活中必不可少的一部分&#xff0c;蓝牙耳机也是现在都市人群几乎人手一个&#xff0c;而在挑选耳机时&#xff0c;相信大家也见过不少 2.4G、5.8G 等名词&#xff0c;那么&#xff0c;蓝牙、2.4G、5.8G 到底有什么关联和区别&…

1.创建项目(wpf视觉项目)

目录 前言本章环境创建项目启动项目可执行文件 前言 本项目主要开发为视觉应用&#xff0c;项目包含&#xff08;视觉编程halcon的应用&#xff0c;会引入handycontrol组件库&#xff0c;工具库Masuit.Tools.Net&#xff0c;数据库工具sqlSugar等应用&#xff09; 后续如果还有…

异步编程 - 04 基于JDK中的Future实现异步编程(上)_Future FutureTask 源码解析

文章目录 概述JDK中的FutureOverViewFuture接口方法详解V get()V get(long timeout&#xff0c;TimeUnit unit)boolean isDone()boolean cancel(boolean mayInterruptIfRunning)boolean isCancelled() JDK中的FutureTaskOverViewFutureTask提交任务到Thread线程执行FutureTask提…

两个类文件,实现根据返回的id显示对应的人员信息增强返回

后台与前台交互时&#xff0c;由于后台存放的原始信息可能就是一些id或code&#xff0c;要经过相应的转换才能很好地在前台展示。 比如&#xff1a; select id, user_id from user 直接返回给前台时&#xff0c;前台可能要根据你这个user_id作为参数&#xff0c;再请求一次后…

DingoDB多模向量数据库,大模型时代的数据觉醒

大模型技术迸发的烟花点燃了整个AI产业链。继各类生成式模型、行业大模型、AI基础软件后&#xff0c;“大模型的海马体”——向量数据库&#xff0c;成为当前最为灼热的AI技术焦点。 在九章云极DataCanvas“变革”产品发布会上重磅亮相的DingoDB多模向量数据库&#xff0c;将多…

杭州高职画室哪家好?如何选择高职画室?高职美术学习选哪家画室?

随着越来越多的画室开始涉足高职美术培训&#xff0c;根据杭州高职画室的美术学生及其家长所知&#xff0c;由于普通高中和高职联考之间存在巨大差异&#xff0c;因此许多普通高中的画室的高职班并未取得太大的成功。因此&#xff0c;小编为正在寻找画室的你提供介绍&#xff1…

pycryptodomex安装过程踩坑解决

前言&#xff1a;装TA&#xff0c;要用pycryptodomex&#xff0c;但出现了toolchain\py版本不匹配&#xff0c;网络上太多方法&#xff0c;五花八门&#xff0c;我需要记录整理下思路&#xff0c;所以作此文 Cryptodome是Python语言的加密和解密库&#xff0c;它是PyCrypto和Cr…

Linux文件系统结构

目录 文件系统结构 当前工作目录&#xff0c;pwd(print work directory) 文件名称&#xff0c;隐藏文件 列出当前目录的内容 ls 查看文件类型 file 绝对路径相对路径&#xff0c;cd 文件系统结构 所有的文件&#xff0c;文件夹&#xff0c;所有的结构都是存在一个叫根目录…

旋转矩阵左乘的理解

关于矩阵左乘和右乘的区别&#xff0c;看了不少数学解释&#xff0c;大概是我水平不够&#xff0c;不是很懂的样子。但本来我也是做应用&#xff0c;抛开理论不谈&#xff0c;看看左乘的实际的使用情况。 1. 关于矩阵及下标的描述 这个非常的重要&#xff0c;如果没有定义好矩…

LeetCode54.螺旋矩阵

这道题一看好像在哪做过一样&#xff0c;好像是写剑指offer里面的状态机的时候写过类似的&#xff0c;就是定义4个方向&#xff0c;它就是按右&#xff0c;下&#xff0c;左&#xff0c;上的规律螺旋的&#xff0c;所以只要拿4个方向给他循环就可以&#xff0c;我是用一个表示方…

多线程应用——线程池

线程池 文章目录 线程池1.什么是线程池2.为什么要用线程池3.怎么使用线程池4.工厂模式5.自己实现一个线程池6.创建系统自带的线程池6.1 拒绝策略6.2 线程池的工作流程 1.什么是线程池 字面意思&#xff0c;一次创建多个线程&#xff0c;放在一个池子(集合类)&#xff0c;用的时…

2023年MySQL实战核心技术第三篇

目录 六 . 事务隔离&#xff1a;为什么改了还看不见&#xff1f; 6.1 解释&#xff1a; 6.2 隔离性与隔离级别 6.2.1 SQL 标准的事务隔离级别&#xff1a; 6.2.2 事务隔离级别解释&#xff1a; 6.2.3 例子&#xff1a; 6.2.3.1 若隔离级别是“读未提交” 6.2.3.2 若隔离级别是“…

无涯教程-JavaScript - BIN2DEC函数

描述 BIN2DEC函数将二进制数字转换为十进制。 语法 BIN2DEC (number)争论 Argument描述Required/Optionalnumber 您要转换的二进制数。 Number cannot contain more than 10 characters (10 bits). 数字的最高有效位是符号位。其余的9位是幅度位。 负数使用二进制补码表示。…

c++(c语言)通用版本的单链表的头插法创建

我们创建一个长度为n的链表时&#xff0c;可以采取头插法创建或者尾插法创建&#xff0c;本篇博客我们采取头插法来创建&#xff0c;&#xff08;作者只学了头插&#xff0c;尾插等以后来补qwq)。 我们先来画图来看看头插的创建形式把&#xff0c;会了原理再写代码。 首先是我…

选择IT行业真的无路可走了吗?

虽说如今IT行业的市场上求职者众多&#xff0c;现在找工作难度也比之前大很多&#xff0c;但这个是大环境趋势&#xff0c;每个行业其实都存在这种情况&#xff0c;而且失业率也高达30%。现在企业一般招聘要求越来越高&#xff0c;各种行业都有劝退人士&#xff0c;劝退不要转行…

文献关系的可视化工具

文章目录 简介网站链接Demo说明数据库 简介 One minute to find a hundred related papers 网站链接 https://www.connectedpapers.com/ Demo 说明 You can use Connected Papers to: Get a visual overview of a new academic field Enter a typical paper and we’ll …

MySQL事务日志--redo, undo详解

事务有 4 种特性&#xff1a;原子性、一致性、隔离性和持久性。那么事务的四种特性到底是基于什么机制实现呢&#xff1f; 事务的隔离性由 锁机制 实现。 而事务的原子性、一致性和持久性由事务的 redo 日志和 undo 日志来保证。 REDO LOG 称为 重做日志 &#xff0c…

亚马逊下架电池,家用及商用电池UL2054检测报告介绍|亚马逊UL2054报告

UL2054是针对可充电电池和电池包的测试和认证项目。该测试项目由美国安全实验室&#xff08;Underwriters Laboratories&#xff09;执行&#xff0c;主要评估电池产品的安全性、性能和符合性。 适用家用及商用电池UL2054检测报告介绍|亚马逊UL2054报告 美国UL电池认证对电池标…

027:vue中两列表数据联动,购物车添加、删除和状态更改

第027个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

敦煌https证书能做些什么

随着互联网技术的不断发展&#xff0c;人们的生活方式和社交方式也发生了巨大的变化。互联网已经成为人们生活中不可或缺的一部分&#xff0c;它不仅提供了方便快捷的信息获取方式&#xff0c;还为人们提供了一个全新的社交平台。 然而&#xff0c;随着互联网的不断发展&#x…