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

概要

本文在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);returnsource 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.rhkb.cn/news/127633.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Cloud:构建微服务的最佳实践

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

Java 基于 SpringBoot 的酒店管理系统,附源码和数据库

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,Csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 一、前言介绍二、系统结构三、系统详细实现3.1用户信息管理3.2会员信息管理3.3客房信息管理3.4收藏…

MySQL——DQL union合并、limit限制与DDL建表和删表

一、Union 合并 union:是实现两个查询结果的合并。 例如&#xff1a;当我们查询员工名字为manager 和 salesman的员工名字和 工作&#xff1f; select e.ename,e.job from emp e where e.jobmanager or e.job salesman; select e.ename,e.job from emp e where e.job in(man…

Notpad++常用正则表达式替换案例集锦

1、在每行的开头加上单引号 2、在每行的结尾加上单引号 3、“删除”某个关键字之前字符串 原始字符串&#xff1a; 注&#xff1a;仅保留含有"[条件日志]:"之后的内容&#xff0c;“日志:”前面的内容“删除”掉&#xff0c;即替换为“”。 4、“删除”某个关键字…

浅谈OPenGL中的纹理过滤

纹理图像中的纹理单元和屏幕上的像素几乎从来不会形成一对一的对应关系。如果程序员足够细心&#xff0c;确实可以实现这个效果&#xff0c;但这需要在对几何图形进行纹理贴图时进行精心的计划&#xff0c;使出现在屏幕上的纹理单元和像素能够对齐&#xff08;实际上在用OPenGL…

搭建HTTPS服务器

HTTPS代理服务器的作用与价值 HTTPS代理服务器可以帮助我们实现网络流量的转发和加密&#xff0c;提高网络安全性和隐私保护。本文将指导您从零开始搭建自己的HTTPS代理服务器&#xff0c;让您更自由、安全地访问互联网。 1. 准备工作&#xff1a;选择服务器与操作系统 a. 选…

Java从入门到精通-数组(二)

4.数组的基本操作 数组的基本操作包括遍历数组、填充替换数组元素、对数组进行排序、复制数组以及查询数组中的元素。 • 4.1 遍历数组 遍历数组是访问数组中所有元素的过程&#xff0c;通常使用循环完成。 使用 for 循环遍历数组&#xff1a; int[] numbers {1, 2, 3, 4…

【测试开发】Mq消息重复如何测试?

本篇文章主要讲述重复消费的原因&#xff0c;以及如何去测试这个场景&#xff0c;最后也会告诉大家&#xff0c;目前互联网项目关于如何避免重复消费的解决方案。 Mq为什么会有重复消费的问题? Mq 常见的缺点之一就是消息重复消费问题&#xff0c;产生这种问题的原因是什么呢…

Spring+MyBatis使用collection标签的两种使用方法

目录 项目场景&#xff1a; 实战操作&#xff1a; 1.创建菜单表 2.创建实体 3.创建Mapper 4.创建xml 属性描述&#xff1a; 效率比较&#xff1a; 项目场景&#xff1a; 本文说明了Spring BootMyBatis使用collection标签的两种使用方法 1. 方法一: 关联查询 2. 方法…

计算机网络基础知识(非常详细)

1. 网络模型 1.1 OSI 七层参考模型 七层模型&#xff0c;亦称 OSI&#xff08;Open System Interconnection&#xff09;参考模型&#xff0c;即开放式系统互联&#xff0c;是网络通信的标准模型。一般称为 OSI 参考模型或七层模型。 它是一个七层的、抽象的模型体&#xff…

【刷题篇】贪心算法(一)

文章目录 分割平衡字符串买卖股票的最佳时机Ⅱ跳跃游戏钱币找零 分割平衡字符串 class Solution { public:int balancedStringSplit(string s) {int lens.size();int cnt0;int balance0;for(int i0;i<len;i){if(s[i]R){balance--;}else{balance;}if(balance0){cnt;}}return …

PDF转Word的方法分享与注意事项。

PDF和Word是两种常用的文档格式&#xff0c;它们各有优点&#xff0c;适用于不同的场景。然而&#xff0c;有时候我们需要将PDF转换为Word&#xff0c;以便更好地进行编辑和排版。本文将介绍几种常用的PDF转Word的方法&#xff0c;并分享一些注意事项。 一、PDF转Word的方法 使…

【系统设计系列】数据库

系统设计系列初衷 System Design Primer&#xff1a; 英文文档 GitHub - donnemartin/system-design-primer: Learn how to design large-scale systems. Prep for the system design interview. Includes Anki flashcards. 中文版&#xff1a; https://github.com/donnemarti…

飞行动力学 - 第18节-全机航向稳定性与隐身性 之 基础点摘要

飞行动力学 - 第18节-全机航向稳定性与隐身性 之 基础点摘要 1. 全机航向静稳定性2. 垂尾与隐身3. 参考资料 1. 全机航向静稳定性 机翼贡献 上反角 复杂、极小幅降低 后掠角 增加稳定性 机身贡献 降低稳定性 尾翼贡献 航向静稳定性的最大来源 平尾 类似机翼贡献 垂尾 最大来…

RESP无法连接linux上redis问题

1.本机无法ping通虚拟机IP 没有打开服务&#xff08;这只是无法ping通虚拟机的一种原因&#xff09; 其他原因可以参考 虚拟机ping不通的几种原因及解决办法_虚拟机ping不通主机_在键盘上弹钢琴的菜菜的博客-CSDN博客 2.未关闭linux系统的防火墙导致无法连接redis 查看防火墙…

【数据结构】绪论

绪论 1.1数据结构的基本概念1.1.1 基本概念和术语1.1.2数据结构的三要素 1.2 算法与算法评价 1.1数据结构的基本概念 1.1.1 基本概念和术语 数据 数据是信息的载体&#xff0c;是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别 和处理的符号的集合。数…

笔记随笔:基于selvlet的Web应用程序流程

前言&#xff1a; 欢迎阅读本文&#xff0c;本文将介绍基于Servlet的Web应用程序的开发流程。Servlet是Java技术中用于处理Web请求和生成动态内容的核心组件之一。通过学习本文&#xff0c;您将了解从项目结构搭建到Servlet类编写、配置和部署的全流程&#xff0c;帮助您快速入…

切面(增强)的优先级

Component Aspect Order(value 10)//为增强类指定一个优先级的值,值越小,优先级越高,优先级越高的前置先执行,后置后执行,类似洋葱 为增强类指定一个优先级的值,值越小,优先级越高,优先级越高的前置先执行,后置后执行,类似洋葱 首先会执行前置通知,再执行目标方法,按照顺序和优…

huggingface 使用入门笔记

概念 Hugging Face Hub​​和 Github 类似&#xff0c;都是Hub(社区)。Hugging Face可以说的上是机器学习界的Github。Hugging Face为用户提供了以下主要功能&#xff1a; ​模型仓库&#xff08;Model Repository&#xff09;​​&#xff1a;Git仓库可以让你管理代码版本、…

【C++深入浅出】类和对象中篇(六种默认成员函数、运算符重载)

目录 一. 前言 二. 默认成员函数 三. 构造函数 3.1 概念 3.2 特性 四. 析构函数 4.1 概念 4.2 特性 五. 拷贝构造函数 5.1 概念 5.2 特性 六. 运算符重载 6.1 引入 6.2 概念 6.3 注意事项 6.4 重载示例 6.5 赋值运算符重载 6.6 前置和后置运算符重载 七. c…