C# Linq源码分析之Take(四)
概要
本文主要对Take的优化方法进行源码分析,分析Take在配合Select,Where等常用的Linq扩展方法使用时候,如何实现优化处理。
本文涉及到Select, Where和Take和三个方法的源码分析,其中Select, Where, Take更详尽的源码分析,请参考我之前写的文章。
源码分析
我们之前对Take的源码分析,主要是真对数据序列对象直接调用的Take方法的情况。本文介绍的Take优化方法,主要是针对多个Linq方法配合使用的情况,例如xx.Where.Select.Take或者xx.Select.Take的情况。
Take的优化方法,是定义在Take.SpeedOpt.cs文件中,体现在下面的TakeIterator方法中,Take方法对TakeIterator方法的具体调用方式,请参考C# Linq源码分析之Take方法
private static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count){Debug.Assert(source != null);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);}
该优化方法的基本逻辑如下:
- 如果source序列实现了IPartition接口,则调用IPartition中的Take方法;
- 如果source序列实现了IList接口,则返回ListPartition对象;
- 返回EnumerablePartition对象。
案例分析
Select.Take
该场景模拟我们显示中将EF中与数据库关联的对象,转换成Web前端需要的对象,并分页的情况。
将Student对象中的学生姓名和考试成绩取出后,并分页,Student类的定义和初始化请见附录。
studentList.Select(x => new {x.Name, x.MathResult}).take(3).ToList();
执行流程如上图所示:
- 进入Select方法后返回一个SelectListIterator对象,该对象存储了source序列数据和selector;
- 进入Take方法后, 因为SelectListIterator实现了IPartition接口,因此可以调用自己的Take方法,使用source,selector和count(Take方法的参数)实例化SelectListPartitionIterator对象;
public IPartition<TResult> Take(int count)
{Debug.Assert(count > 0);int maxIndex = _minIndexInclusive + count - 1;return (uint)maxIndex >= (uint)_maxIndexInclusive ? this : new SelectListPartitionIterator<TSource, TResult>(_source, _selector, _minIndexInclusive, maxIndex);
}
- 进入ToList方法后,根据下面的代码,因为SelectListPartitionIterator对象实现了IPartition接口,而IPartition又继承了IIListProvider接口,所以listProvider.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);
}
listProvider.ToList() 被执行,即SelectListPartitionIterator对象内定义的ToList()方法被执行,该ToList方法可以将
Selecxt的投影操作,Take的取值操作同时执行,代码如下:
public List<TResult> ToList()
{int count = Count;if (count == 0){return new List<TResult>();}List<TResult> list = new List<TResult>(count);int end = _minIndexInclusive + count;for (int i = _minIndexInclusive; i != end; ++i){list.Add(_selector(_source[i]));}return list;
}
- 如果Take的取值为0,直接返回空List,不会进行Select操作;
- 按照Take中指定的Count,取到相应的元素,再进行投影操作;
- 返回操作结果。
这样,源List序列source,Linq只遍历和一次,就同时完成了投影和过滤两个操作,实现了优化。
附录
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);
}
SelectListPartitionIterator类
private sealed class SelectListPartitionIterator<TSource, TResult> : Iterator<TResult>, IPartition<TResult>
{private readonly IList<TSource> _source;private readonly Func<TSource, TResult> _selector;private readonly int _minIndexInclusive;private readonly int _maxIndexInclusive;public SelectListPartitionIterator(IList<TSource> source, Func<TSource, TResult> selector, int minIndexInclusive, int maxIndexInclusive){Debug.Assert(source != null);Debug.Assert(selector != null);Debug.Assert(minIndexInclusive >= 0);Debug.Assert(minIndexInclusive <= maxIndexInclusive);_source = source;_selector = selector;_minIndexInclusive = minIndexInclusive;_maxIndexInclusive = maxIndexInclusive;}public override Iterator<TResult> Clone() =>new SelectListPartitionIterator<TSource, TResult>(_source, _selector, _minIndexInclusive, _maxIndexInclusive);public override bool MoveNext(){// _state - 1 represents the zero-based index into the list.// Having a separate field for the index would be more readable. However, we save it// into _state with a bias to minimize field size of the iterator.int index = _state - 1;if (unchecked((uint)index <= (uint)(_maxIndexInclusive - _minIndexInclusive) && index < _source.Count - _minIndexInclusive)){_current = _selector(_source[_minIndexInclusive + index]);++_state;return true;}Dispose();return false;}public override IEnumerable<TResult2> Select<TResult2>(Func<TResult, TResult2> selector) =>new SelectListPartitionIterator<TSource, TResult2>(_source, CombineSelectors(_selector, selector), _minIndexInclusive, _maxIndexInclusive);public IPartition<TResult> Skip(int count){Debug.Assert(count > 0);int minIndex = _minIndexInclusive + count;return (uint)minIndex > (uint)_maxIndexInclusive ? EmptyPartition<TResult>.Instance : new SelectListPartitionIterator<TSource, TResult>(_source, _selector, minIndex, _maxIndexInclusive);}public IPartition<TResult> Take(int count){Debug.Assert(count > 0);int maxIndex = _minIndexInclusive + count - 1;return (uint)maxIndex >= (uint)_maxIndexInclusive ? this : new SelectListPartitionIterator<TSource, TResult>(_source, _selector, _minIndexInclusive, maxIndex);}public TResult? TryGetElementAt(int index, out bool found){if ((uint)index <= (uint)(_maxIndexInclusive - _minIndexInclusive) && index < _source.Count - _minIndexInclusive){found = true;return _selector(_source[_minIndexInclusive + index]);}found = false;return default;}public TResult? TryGetFirst(out bool found){if (_source.Count > _minIndexInclusive){found = true;return _selector(_source[_minIndexInclusive]);}found = false;return default;}public TResult? TryGetLast(out bool found){int lastIndex = _source.Count - 1;if (lastIndex >= _minIndexInclusive){found = true;return _selector(_source[Math.Min(lastIndex, _maxIndexInclusive)]);}found = false;return default;}private int Count{get{int count = _source.Count;if (count <= _minIndexInclusive){return 0;}return Math.Min(count - 1, _maxIndexInclusive) - _minIndexInclusive + 1;}}public TResult[] ToArray(){int count = Count;if (count == 0){return Array.Empty<TResult>();}TResult[] array = new TResult[count];for (int i = 0, curIdx = _minIndexInclusive; i != array.Length; ++i, ++curIdx){array[i] = _selector(_source[curIdx]);}return array;}public List<TResult> ToList(){int count = Count;if (count == 0){return new List<TResult>();}List<TResult> list = new List<TResult>(count);int end = _minIndexInclusive + count;for (int i = _minIndexInclusive; i != end; ++i){list.Add(_selector(_source[i]));}return list;}public int GetCount(bool onlyIfCheap){// In case someone uses Count() to force evaluation of// the selector, run it provided `onlyIfCheap` is false.int count = Count;if (!onlyIfCheap){int end = _minIndexInclusive + count;for (int i = _minIndexInclusive; i != end; ++i){_selector(_source[i]);}}return count;}
}
SelectListIterator类 Select.cs
private sealed partial class SelectListIterator<TSource, TResult> : Iterator<TResult>{private readonly List<TSource> _source;private readonly Func<TSource, TResult> _selector;private List<TSource>.Enumerator _enumerator;public SelectListIterator(List<TSource> source, Func<TSource, TResult> selector){Debug.Assert(source != null);Debug.Assert(selector != null);_source = source;_selector = selector;}private int CountForDebugger => _source.Count;public override Iterator<TResult> Clone() => new SelectListIterator<TSource, TResult>(_source, _selector);public override bool MoveNext(){switch (_state){case 1:_enumerator = _source.GetEnumerator();_state = 2;goto case 2;case 2:if (_enumerator.MoveNext()){_current = _selector(_enumerator.Current);return true;}Dispose();break;}return false;}public override IEnumerable<TResult2> Select<TResult2>(Func<TResult, TResult2> selector) =>new SelectListIterator<TSource, TResult2>(_source, CombineSelectors(_selector, selector));}
Select.SpeedOpt.cs
private sealed partial class SelectListIterator<TSource, TResult> : IPartition<TResult>{public TResult[] ToArray(){int count = _source.Count;if (count == 0){return Array.Empty<TResult>();}var results = new TResult[count];for (int i = 0; i < results.Length; i++){results[i] = _selector(_source[i]);}return results;}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;}public int GetCount(bool onlyIfCheap){// In case someone uses Count() to force evaluation of// the selector, run it provided `onlyIfCheap` is false.int count = _source.Count;if (!onlyIfCheap){for (int i = 0; i < count; i++){_selector(_source[i]);}}return count;}public IPartition<TResult> Skip(int count){Debug.Assert(count > 0);return new SelectListPartitionIterator<TSource, TResult>(_source, _selector, count, int.MaxValue);}public IPartition<TResult> Take(int count){Debug.Assert(count > 0);return new SelectListPartitionIterator<TSource, TResult>(_source, _selector, 0, count - 1);}public TResult? TryGetElementAt(int index, out bool found){if (unchecked((uint)index < (uint)_source.Count)){found = true;return _selector(_source[index]);}found = false;return default;}public TResult? TryGetFirst(out bool found){if (_source.Count != 0){found = true;return _selector(_source[0]);}found = false;return default;}public TResult? TryGetLast(out bool found){int len = _source.Count;if (len != 0){found = true;return _selector(_source[len - 1]);}found = false;return default;}}
相关文章:

C# Linq源码分析之Take(四)
概要 本文主要对Take的优化方法进行源码分析,分析Take在配合Select,Where等常用的Linq扩展方法使用时候,如何实现优化处理。 本文涉及到Select, Where和Take和三个方法的源码分析,其中Select, Where, Take更详尽的源码分析&…...
Python 和 C++ 使用细节差别
1. 循环中的可迭代对象长度 1. 循环中的可迭代对象长度 C 中,for循环中写明a.size(),每次循环这个值是重新计算的; # include “iostream” # include <vector> using namespace std;int main() {vector<int> a(10);int cnt 0…...

在Ubuntu Linux系统上安装RabbitMQ服务并解决公网远程访问问题
文章目录 前言1.安装erlang 语言2.安装rabbitMQ3. 内网穿透3.1 安装cpolar内网穿透(支持一键自动安装脚本)3.2 创建HTTP隧道 4. 公网远程连接5.固定公网TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址 前言 RabbitMQ是一个在 AMQP(高级消息队列协议)基…...

葫芦娃自动预约-公众号代挂
效果 #小程序://航旅黔购/1nkYlNRVzm0Gg9x #小程序://贵旅优品/7zz6mtnSVgDfyqa #小程序://新联惠购/ibFdsuhWqIbczEd #小程序://贵盐黔品/u2TgExCUdkavrFe #小程序://空港乐购/ANkOOdqEeo71kah #小程序://遵航出山/ZkR7DQy1raoPxKD #小程序://乐旅商城/Ip5cgpJ7TLmRrWF #小程序…...

ESP32应用教程(0)— PMW3901MB光流传感器
文章目录 前言 1 传感器介绍 1.1 关键特征 1.2 关键参数 2 硬件概述 2.1 信号引脚 2.2 参考电路图 3 寄存器 3.1 寄存器列表 3.2 性能优化寄存器 4 代码说明 4.1 结构体说明 4.2 编译说明 5 波形分析 前言 本文介绍了在 ESP32 DEVKIT V1 开发板上开发 PMW3901MB…...

docker部署nginx,部署springboot项目,并实现访问
一、先部署springboot项目 1、安装docker: yum install docker -y 2、启动docker: service docker start 重启: service docker restart 3、查看版本: docker -v 4、使设置docker.service生效(路径:…...

十五、模板方法模式
一、什么是模板方法模式 模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。 模板方法模式包含以…...

jvm 什么是常量池,常量池定义 class常量池
首先需要理解下Java的class文件,以及class文件结构: 1.Class文件是一组以8个字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文 件之中,中间没有任何分隔符,这使得整个Class文件中存储的内容几乎全部…...

CA证书颁发机构服务器
目录 一、CA证书颁发机构是什么? 二、数字证书可以干什么? 三、PKI:即公钥加密体系(public key cryptography) 四、CA在网络中的工作流程及原理(以网站为例) 五、HTTPS 的工作原理 六、CA私有证…...

5. 线性层及其他层
5.1 神经网络结构 5.2 线性拉平 import torch import torchvision from torch import nn from torch.nn import ReLU from torch.nn import Sigmoid from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriterdataset torchvision.datase…...

PhpStorm安装篇
PhpStorm安装篇: 下载地址 : 进入官网下PhpStorm: PHP IDE and Code Editor from JetBrains 下载完之后,安装包 安装目录建议不要放C盘,放其他盘,其他直接一直点 next ,到结束 安装完,打开编辑器 注册账号并登录后…...
麒麟Linux常见问题
文章目录 麒麟Linux常见问题 1. yum下载不了文件1)报错信息2)原因3)解决(1)更换下载源(2)缓存处理 4)验证 2. 麒麟Linux常见问题 1. yum下载不了文件 1)报错信息 faile…...

一百六十八、Kettle——用海豚调度器定时调度从Kafka到HDFS的任务脚本(持续更新追踪、持续完善)
一、目的 在实际项目中,从Kafka到HDFS的数据是每天自动生成一个文件,按日期区分。而且Kafka在不断生产数据,因此看看kettle是不是需要时刻运行?能不能按照每日自动生成数据文件? 为了测试实际项目中的海豚定时调度从…...

Linux centos7 bash编程(小练习)
一、打印九九乘法口诀 这一个for循环嵌套的小练习,难度不大。提供一种写法,供参考: #!/bin/bash # 文件名:99table.sh # 打印输出九九乘法口诀表 for i in {1..9} do for ((j1;j<$i;j)) do …...
【SpringBoot】Web server failed to start. Port 8080 was already in use.
问题描述 SpringBoot启动Web服务器失败。 *************************** APPLICATION FAILED TO START ***************************Description:Web server failed to start. Port 8080 was already in use.Action:Identify and stop the process thats listening on port 80…...
day-36 代码随想录算法训练营(19)part05
435.无重叠区间 思路:首先对数组排序,只需要关注重叠区间就行,有重叠时计数1,然后更新当前右边界为重叠区间中的最小右边界。 763.划分字母区间 思路:记录每一个字母的最远位置,然后从头开始遍历…...
Vue3 实现JS动态改变CSS样式
以颜色为例子 定义颜色变量 import { reactive } from vue; // 可变的主题颜色 let chooseColor reactive({--color: #be2a27 }) CSS中使用 var() 函数引用颜色变量(这里是用elementPlus为例, 也可以改) :deep(.is-active) {color: var(--color);border-bottom: 2px solid…...

最新社区团购电商小程序源码 无bug完美运营版+详细搭建部署教程
分享一个开源社区团购电商小程序源码,无bug完美运营版,含完整前后端详细搭建部署教程。 系统运营模式:整合线下社区资源,由各快递代收点、社区便利店、社区物业、业主等发起的社区微信群,推送商品信息,消费…...

恒运资本:三大指数震荡走低,地产股大幅回撤,光刻胶概念逆市上涨
周四(8月31日),到上午收盘,A股三大指数震动走低。其间,上证指数跌0.53%,报3120.39点;深证成指和创业板指别离跌0.55%、0.54%;沪深两市算计成交额5290.51亿元,总体来看&am…...

DP读书:不知道干什么就和我一起读书吧——以《鲲鹏处理器 架构与编程》中鲲鹏软件的构成为例
DP读书:不知道干什么就和我一起读书吧 为啥写博客:好处一:记录自己的学习过程优点二:让自己在各大社群里不那么尴尬推荐三:坚持下去,找到一个能支持自己的伙伴模版:鲲鹏软件构成硬件特定软件1. …...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...

现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...