C# Linq源码解析之Aggregate

前言

在Dotnet开发过程中,Aggregate作为IEnumerable的扩展方法,十分常用。本文对Aggregate方法的关键源码进行简要分析,以方便大家日后更好的使用该方法。

使用

Aggregate是对序列应用累加器的函数。

看下面一段代码:

List<string> lst = new List<string>() { "张三", "李四", "王麻子" };

给了我们这样的一个list集合,我们想要得到

"张三 哈哈哈 李四 哈哈哈 王麻子 "

这样的一个结果;

假如我们没有linq,我们该怎么去写这个代码呢?

可能和下面这段代码没有多少出入!

string str = "";for (int i = 0; i < lst.Count; i++)
{str += lst[i] + " 哈哈哈 ";
}
Console.WriteLine(str);

那如果使用linq的扩展方法Aggregate就显得简单的多了

str = lst.Aggregate((first, second) => $"{first} 哈哈哈 {second} ");

那我肯定不只是给大家介绍这个东西怎么写的,我们是来刨析它的原理的。我们来看看他的原型

public static TSource Aggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func)

Func<TSource, TSource, TSource> func 可以看出方法接收一个委托,该委托接收两个TSource类型的参数,第一个参数是记录值,第二个参数是从IEnumerable中取下一个元素,最后一个是返回累加器的最终值。

可能有人对前面的lambda怎么演变的不清楚

(first, second) => $"{first} 哈哈哈 {second} "

我这里再讲一下,具体的内容请看我的文章你真的了解Lambda表达式吗?

其实这个lambda表达式转换成方法就是这样的

public string MyAdd(string first, string second){return $"{first} 哈哈哈 {second} " ;}

然后这样去使用,输出的结果也是一样的

str = lst.Aggregate(MyAdd);Console.WriteLine(str);

看到这里你可能还是很疑惑,那Aggregate内部的运行原理是怎样的呢?我们定义一个扩展方法跟Aggregate的原型一样我们来一步一步的实现它。我们声明一个MyAggregate

public static TSource MyAggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func)

我们来一步一步的实现它的源码

我们方法内部肯定是要对参数进行判空校验的

public static TSource MyAggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func)
{if (source == null){throw new Exception("source is null");}if (func == null){throw new Exception("func is null");}
}

扩展方法是静态类的静态方法,其实第一个参数用this修饰符代表源元素的类型,且是一个可进行迭代的源元素类型

this IEnumerable<TSource> source

那么我们就知道这个源元素是一个 可以获得循环访问集合的枚举器那么我们就可以使用GetEnumerator这个方法进行迭代了,虽然我们对源元素进行了判空,我们程序为了严谨性,我们肯定要对源元素里面是否有内容进行判空

using (IEnumerator<TSource> enumerator = source.GetEnumerator()){if (!enumerator.MoveNext()){throw new Exception("enumerator is null");}}

然后我们声明一个返回的类型接收当前值,然后使用while循环遍历源元素的值,最后返回值,这样我们的Aggregate源码就实现了

public static TSource MyAggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func){if (source == null){throw new Exception("source is null");}if (func == null){throw new Exception("func is null");}using (IEnumerator<TSource> enumerator = source.GetEnumerator()){if (!enumerator.MoveNext()){throw new Exception("enumerator is null");}TSource val = enumerator.Current;while (enumerator.MoveNext()){val = func(val, enumerator.Current);}return val;}}

我们的Aggregate是有三个重载方法的,我们实现了最简单的一个

那我们来看第二个

public static TAccumulate Aggregate<TSource, TAccumulate>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func

就比第一个方法多了Accumulate seed 将指定的种子值用作累加器初始值。

然后我们能使用这个方法

str = lst.Aggregate("我的输出结果", (first, second) => $"{first} 哈哈哈 {second} ");Console.WriteLine(str);

那怎么去理解这个将指定的种子值用作累加器初始值,下面的这段代码和上面等同

str = "我的输出结果"+lst.Aggregate( (first, second) => $"{first} 哈哈哈 {second} ");Console.WriteLine(str);

我们就可以实现该源码了,由于我们初始值有了,所以不用对元素里面是否有内容进行判空,直接使用foreach遍历

public static TAccumulate Aggregate<TSource, TAccumulate>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func){if (source == null){throw Error.ArgumentNull("source");}if (func == null){throw Error.ArgumentNull("func");}TAccumulate val = seed;foreach (TSource item in source){val = func(val, item);}return val;

那我们再来看第三个

public static TResult Aggregate<TSource, TAccumulate, TResult>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func, Func<TAccumulate, TResult> resultSelector)

对序列应用累加器函数。将指定的种子值用作累加器的初始值,并使用指定的函数处理结果值。比第二个方法多了一个委托

Func<TAccumulate, TResult> resultSelector

然后我们来使用该方法

str = lst.Aggregate("我的输出结果",(first, second) => $"{first} 哈哈哈 {second} ",end=> end +"我是尾巴");

然后我们来转换一下,方便大家理解,

str = (lst.Aggregate("我的输出结果",(first, second) => $"{first} 哈哈哈 {second} "))+ "我是尾巴";

为什么我这里加了一个大括号,因为后面这个委托是对结果值进行一个整体处理,可能这样举例不太恰当,如果我们返回的结果是小写字母。我们就可以对整体结果进行一个小写转大写的操作

这样可能理解就更深刻了

str = lst.Aggregate("我的输出结果",(first, second) => $"{first} 哈哈哈 {second} ",end=> end.ToUpper());str = (lst.Aggregate("我的输出结果",(first, second) => $"{first} 哈哈哈 {second} ")).ToUpper();

然后它的源码实现也比较简单了,就比第二个方法多了一个对结果值的处理,修改一下上面的源码

public static TResult Aggregate<TSource, TAccumulate, TResult>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func, Func<TAccumulate, TResult> resultSelector){if (source == null){throw Error.ArgumentNull("source");}if (func == null){throw Error.ArgumentNull("func");}if (resultSelector == null){throw Error.ArgumentNull("resultSelector");}TAccumulate val = seed;foreach (TSource item in source){val = func(val, item);}return resultSelector(val);}

我们发现Aggregate只实现了

  • Aggregate(IEnumerable, Func<TSource,TSource,TSource>) 对序列应用累加器函数。

  • Aggregate<TSource,TAccumulate>(IEnumerable, TAccumulate, Func<TAccumulate,TSource,TAccumulate>) 对序列应用累加器函数。将指定的种子值用作累加器初始值。

  • Aggregate<TSource,TAccumulate,TResult>(IEnumerable, TAccumulate, Func<TAccumulate,TSource,TAccumulate>, Func<TAccumulate,TResult>)对序列应用累加器函数。将指定的种子值用作累加器的初始值,并使用指定的函数选择结果值

并没有实现 :对序列应用累加器函数,不使用指定种子的累加器初始值,且使用指定的函数选择结果值。那看了我的源码解析,是不是很容易实现了

public static TResult MyAggregate<TSource, TResult>(this IEnumerable<TSource> source,  Func<TSource, TSource, TSource> func, Func<TSource, TResult> resultSelector){if (source == null){throw new Exception("source is null");}if (func == null){throw new Exception("func is null");}using (IEnumerator<TSource> enumerator = source.GetEnumerator()){if (!enumerator.MoveNext()){throw new Exception("enumerator is null");}TSource val = enumerator.Current;while (enumerator.MoveNext()){val = func(val, enumerator.Current);}return resultSelector(val);}}

如果还不明白对序列应用累加器函数。并使用指定的函数选择结果值,建议大家再看一遍.

最后大家如果喜欢我的文章,还麻烦给个关注并点个赞, 希望net生态圈越来越好!

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

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

相关文章

c语言输出中文为乱码_C语言编程出现汉字输出乱码现象

匿名用户1级2014-06-27 回答//因为不知道你具体的功能流程&#xff0c;所以我只能先就语法来提下代码的问题int cha(){//int a[N],b[N];//int i0,x,v1,v2;char a[N][M] {0};//M为字符串最大长度加1,根据要求设置int b[N] {0};int i0,x,v2;char v1[M] {0};FILE *f;if((ffopen…

剑指offer之二叉树的高度

1 问题 求二叉树的深度&#xff0c;比如下面的二叉树&#xff0c;高度是4 22 13 3 2 53 2 代码实现 int getTreeHeigh(Node *haed) {if (head NULLL){return 0;}int left getTreeHeigh(head->left);int right getTreeHeigh(head->right);retur…

Entity Framework Code First模式基础知识及入门实例01

在深入学习某项技术之前,应该努力形成对此技术的总体印象,并了解其基本原理,本文的目的就在于此。 一、理解EF数据模型 EF本质上是一个ORM框架,它需要把对象映射到底层数据库中的表,为此,它使用了三个模型来描述这种映射关系。 (1)概念模型(Conceptual Model):主要…

mycat 双主 热切换

为什么80%的码农都做不了架构师&#xff1f;>>> Mycat-server-1.6-RELEASE-20161028204710-linux.tar.gz schema.xml <?xml version"1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat"ht…

Web程序员的Mysql进阶序三之sql多表数据删除、子查询、联合查询

假设表结构如下&#xff1a; create table test(name varchar(10),sex varchar(10) ); create table test1(name varchar(10),sex varchar(10) );假设多条数据同时插入&#xff1a; insert into test (name,sex) values(xiao,nan),(xiao1,nan1),(xiao2,nan2); insert into te…

android 传感器 balance filter,Android 传感器 API.doc

Android 传感器 API1.1 序言 ? ?【Android蛋蛋网】在这一章我们主要针对Android的传感器(Sensor)进行说明。如果说Java的程序和硬件有些远的感觉&#xff0c;那么可以说传感器距离硬件很近。传感器(Sensor)听起来似乎感觉很难&#xff0c;其实它并不是很难。做起来并很有意思…

6 四大组件之Service

6-1 Servie概述 组件篇——Service 定义:  1.后台运行,不可见,没有界面  2.优先级高于Activity Service是Android系统的后台服务组件&#xff0c;适用于开发无界面、长时间运行的应用功能。 Service特点如下&#xff1a; 没有用户界面 不会轻易被Android系统终止 在系统…

WCF服务寄宿IIS时.SVC文件无法浏览的解决办法

在IIS中托管服务和经典的ASMX Web服务托管相似,需要在IIS下创建虚拟目录,并提供一个.svc文件和Web.config配置文件。另外,托管时,服务的基地址必须与.svc文件的地址相同。 一、操作步骤: (1)打开IIS,新建网站,选择物理路径 (2)打开IIS,在默认网站下添加应用程序,…

python自动生成鸡汤文_用20行Python代码生成鸡汤,打造AI咪蒙指日可待。

作者&#xff1a;Ramtin Alami“Don’t think of the overwhelming majority of the impossible.” “不要去想不可能之事” “Grew up your bliss and the world.” “努力赢得自己的幸福和世界” “what we would end create, creates the ground and you are the one to war…

他毕业两年,博客一年,时间

这是2014年10个月。时光匆匆&#xff0c;看到该公司的新鲜新的一年。有时&#xff0c;一个想法。其实&#xff0c;我在这个城市工作了两年多&#xff0c;总是觉得自己老了。从一般再聘请两名现在厂家机构和学校&#xff0c;懵懵懂懂从校园到完成社区&#xff0c;来写个总结&…

linux shell之替换目录下包含关键字所有文本里面的内容

1 问题 替换目录下包含关键字所有文本里面的内容 比如在目录 /root/chenyu/cy/下&#xff0c;把包含文本/A/B C替换成文本EF 2 解决办法 命令如下 grep -Rl /A/B\ C /root/chenyu/c* | xargs sed -i s#/A/B\ C#EF#g 我们也可以这样 grep -Rl /A/B\ C /root/chenyu/c/ | x…

C#语法糖系列 —— 第一篇:聊聊 params 参数底层玩法

首先说说为什么要写这个系列&#xff0c;大概有两点原因。这种文章阅读量确实高...对 IL 和 汇编代码 的学习巩固所以就决定写一下这个系列&#xff0c;如果大家能从中有所收获&#xff0c;那就更好啦&#xff01;一&#xff1a;params 应用层玩法 首先上一段 测试代码。class …

nginx 修改配置文件使之支持pathinfo,且隐藏index.php

声明环境&#xff1a; nginx centos6.8 使用lnmp一键包搭建环境&#xff08;2019年2月19日 &#xff09;以前使用过别的办法去修改配置文件&#xff0c;但是过于繁琐&#xff0c;最近发现新版本中&#xff0c;在nginx 的 conf目录下发现了文件“enable-php-pathinfo.conf”&am…

Android开发经典笔试面试题汇总(持续更新中)

1.我们都知道Handler是线程与Activity通信的桥梁&#xff0c;假设线程处理不当。你的机器就会变得非常慢&#xff0c;那么线程销毁的方法是&#xff1a;&#xff08;A&#xff09;A. onDestroy()B. onClear()C. onFinish()D. onStop()理解&#xff1a;正确销毁线程的方法应该是…

WCF服务自托管(Self-Hosting)

WCF寄宿方式是一种非常灵活的操作,可以寄宿在各种进程之中,常见的寄宿有:IIS服务、Windows服务、Winform程序、控制台程序中进行寄宿,从而实现WCF服务的运行,为调用者方便、高效提供服务调用。 当客户端和服务端之间的进程有明确的边界时,使用自托管,需要注意的…

学习进度04

第五周所花时间&#xff08;分钟&#xff09;100代码量&#xff08;行&#xff09;50博客量&#xff08;篇&#xff09;2了解到的知识点作为合作的团队要制定统一的标准转载于:https://www.cnblogs.com/liguoshuai/p/5352590.html

android 微信分享gif图,android后台动态创建图片并实现微信分享

今天就记录一下。先说明一下&#xff0c;之前没有做过类似的东西&#xff0c;百度了一两天才知道&#xff0c;说来很惭愧、有点笨&#xff0c;只能这样说。在我的脑里只明白&#xff0c;如果要动态创建图片&#xff1a;一、就是new 嘛二、就是LayoutInflater.from()这种。而微信…

python字符串前面去两位_在Python 3中删除字符串文字前面的'b'字符do

I am new in python programming and i am a bit confused. I try to get the bytes from a string to hash and encrypt but i gotb...b character in front of string just like the below example. Is any way avoid this?.Can anyone give a solution? Sorry for this si…

C语言之去掉https链接的默认443端口

1 问题 去掉https链接的默认443端口 2 代码实现 #include <stdio.h> #include <string.h> #include <stdlib.h>#define BOOL int #define TRUE 1 #define FALSE 0/**判断字符串str1是不是str2开头*/ int is_begin_with(const char *str1, char *str2) {if(…

仅需一个参数就可搞定OneProxy的VIP机制

文章转自&#xff1a;http://card.weibo.com/article/h5/s#cid1001603863326047255626&vid&extparam&from&wm0&ip182.50.119.226现在都讲求无单点的架构&#xff0c;OneProxy也不例外&#xff0c;虽然已经有进程级别的自动重起机制&#xff08;--keepalive…