Enumerable 下又有新的扩展方法啦,快来一起一睹为快吧
一:背景
1. 讲故事
前段时间将公司的一个项目从 4.5 升级到了 framework 4.8 ,编码的时候发现 Enumerable 中多了三个扩展方法: Append, Prepend, ToHashSet,想必玩过jquery的朋友一眼就能看出这三个方法的用途,这篇就和大家一起来聊聊这三个方法的底层源码实现,看有没有什么新东西可以挖出来。
二:Enumerable 下的新扩展方法
1. Append
看到这个我的第一印象就是 Add 方法, 可惜在 Enumerable 中并没有类似的方法,可能后来程序员在这块的呼声越来越高,C#开发团队就弥补了这个遗憾。
<1> 单条数据的追加
接下来我写一个小例子往集合的尾部追加一条数据,如下代码所示:
static void Main(string[] args)
{
var arr = new int[2] { 1, 2 };
var result = Enumerable.Append(arr, 3);
foreach (var item in result)
{
Console.WriteLine(item);
}
}

逻辑还是非常清晰的,再来看看底层源码是怎么实现的。
public static IEnumerable<TSource> Append<TSource>(this IEnumerable<TSource> source, TSource element)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
AppendPrependIterator<TSource> appendPrependIterator = source as AppendPrependIterator<TSource>;
if (appendPrependIterator != null)
{
return appendPrependIterator.Append(element);
}
return new AppendPrepend1Iterator<TSource>(source, element, appending: true);
}
private class AppendPrepend1Iterator<TSource> : AppendPrependIterator<TSource>
{
public AppendPrepend1Iterator(IEnumerable<TSource> source, TSource item, bool appending) : base(source)
{
_item = item;
_appending = appending;
}
public override bool MoveNext()
{
switch (state)
{
case 1:
state = 2;
if (!_appending)
{
current = _item;
return true;
}
goto case 2;
case 2:
GetSourceEnumerator();
state = 3;
goto case 3;
case 3:
if (LoadFromEnumerator())
{
return true;
}
if (_appending)
{
current = _item;
return true;
}
break;
}
Dispose();
return false;
}
}
从上面的源码来看,这玩意做的还是挺复杂的,继承关系依次是: AppendPrepend1Iterator<TSource> -> AppendPrependIterator<TSource> -> Iterator<TSource>, 这里大家要着重看一下 MoveNext() 里面的两个方法 GetSourceEnumerator() 和 LoadFromEnumerator(),如下代码所示:

可以看到,第一个方法用于获取 Array 这个数据源,下面这个方法用于遍历这个 Array,当 foreach 遍历完之后,执行 case 3 语句,也就是下面的 if 语句,将你追加的 3 迭代一下,如下图:

<2> 批量数据的追加
我们知道集合的添加除了 Add 还有 AddRange,很遗憾,Enumerable下并没有找到类似的 AppendRange 方法,那如果要实现 AppendRange 操作该怎么处理呢? 哈哈,只能自己 foreach 迭代啦,如下代码:
static void Main(string[] args)
{
var arr = new int[2] { 1, 2 };
var arr2 = new int[3] { 3, 4, 5 };
IEnumerable<int> collection = arr;
foreach (var item in arr2)
{
collection = collection.Append(item);
}
foreach (var item in collection)
{
Console.WriteLine(item);
}
}

结果也是非常简单的,因为 IEnumerable 是非破坏性的操作,所以你需要在 Append 之后用类型给接住,接下来找一下底层源码。
public static IEnumerable<TSource> Append<TSource>(this IEnumerable<TSource> source, TSource element)
{
if (source == null)
{
throw Error.ArgumentNull("source");
}
AppendPrependIterator<TSource> appendPrependIterator = source as AppendPrependIterator<TSource>;
if (appendPrependIterator != null)
{
return appendPrependIterator.Append(element);
}
return new AppendPrepend1Iterator<TSource>(source, element, appending: true);
}
private class AppendPrepend1Iterator<TSource> : AppendPrependIterator<TSource>
{
public override AppendPrependIterator<TSource> Append(TSource item)
{
if (_appending)
{
return new AppendPrependN<TSource>(_source, null, new SingleLinkedNode<TSource>(_item).Add(item), 0, 2);
}
return new AppendPrependN<TSource>(_source, new SingleLinkedNode<TSource>(_item), new SingleLinkedNode<TSource>(item), 1, 1);
}
}
private class AppendPrependN<TSource> : AppendPrependIterator<TSource>
{
public override AppendPrependIterator<TSource> Append(TSource item)
{
SingleLinkedNode<TSource> appended = (_appended != null) ? _appended.Add(item) : new SingleLinkedNode<TSource>(item);
return new AppendPrependN<TSource>(_source, _prepended, appended, _prependCount, _appendCount + 1);
}
}
从上面的代码可以看出,当你 Append 多次的时候,本质上就是多次调用 AppendPrependN<TSource>.Append() ,而且在调用的过程中,一直将你后续添加的元素追加到 SingleLinkedNode 单链表中,这里要注意的是 Add 采用的是 头插法,所以最后插入的元素会在队列头部,如下图:

如果你不信的话,我可以在 vs 调试中给您展示出来。

貌似说的有点啰嗦,最后大家观察一下 AppendPrependN<TSource>.MoveNext 的实现就可以了。

说了这么多,我想你应该明白了哈。
2. Prepend
本质上来说 Prepend 和 Append 是一对的,一个是在前面插入,一个是在后面插入,不要想歪了,如果你细心的话,你会发现 Prepend 也是用了这三个类: AppendPrepend1Iterator<TSource>,AppendPrependIterator<TSource>,AppendPrependN<TSource> 以及 单链表 SingleLinkedNode<TSource>,这个就留给大家自己研究了哈。
3. ToHashSet
我以前在全内存开发中会频繁的用到 HashSet,毕竟它的时间复杂度是 O(1) ,而且在 Enumerable 中早就有了 ToList 和 ToDictionary,凭啥没有 ToHashSet,在以前只能将 source 塞到 HashSet 的构造函数中,如: new HashSet<int>(source) ,想想也是够奇葩的哈,而且我还想吐糟一下的是居然到现在还没有 AddRange 批量添加方法,气人哈,接下来用 ILSpy 看一下这个扩展方法是如何实现的。

三: 总结
总体来说这三个方法还是很实用的,我相信在后续的版本中 Enumerable 下的扩展方法还会越来越多,越来越人性化,人生苦短, 我用C#。
如您有更多问题与我互动,扫描下方进来吧~

Enumerable 下又有新的扩展方法啦,快来一起一睹为快吧的更多相关文章
- EF下lambda与linq查询&&扩展方法
1. linq查询数据 WebTestDBEntities db = new WebTestDBEntities(); 1.1 linq查询所有列数据 var userInfoList = from ...
- CentOS下安装PHP的AMQP扩展方法和步骤
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计.基于此协议的客户端 ...
- Windows下配置PHP支持LDAP扩展方法(wampserver)
在网上搜了好多文章都不行呢,大都是没有开启扩展的问题,可是我的是开启的. 终于看到一篇文章,因为我用的是wampserver.下面是文章原话: 然后你发现上面的提示依旧,因为这是网上大多能查到的资料的 ...
- ASP.NET MVC学前篇之扩展方法、链式编程
ASP.NET MVC学前篇之扩展方法.链式编程 前言 目的没有别的,就是介绍几点在ASP.NETMVC 用到C#语言特性,还有一些其他琐碎的知识点,强行的划分一个范围的话,只能说都跟MVC有关,有的 ...
- IEnumerable<T>与IQueryable<T>以及.net的扩展方法
首先看看继承关系 public abstract class DbSet : DbQuery public abstract class DbQuery : IOrderedQueryable, IQ ...
- LINQ学习系列-----1.3 扩展方法
这篇内容继续接着昨天的Lambda表达式的源码继续下去.昨天讲了Lambda表达式,此篇讲扩展方法,这两点都是Linq带来的新特性. 一.扩展方法介绍 废话不多说,先上源码截图: 上图中Ge ...
- Linq扩展方法获取单个元素
在使用Linq 提供的扩展方法时,First(OrDefault), Single(OrDefault), Last(OrDefault)都具有返回单个元素的功能.MSDN对这些方法的描述只有功能说明 ...
- 关于.NET中迭代器的实现以及集合扩展方法的理解
在C#中所有的数据结构类型都实现IEnumerable或IEnumerable<T>接口(实现迭代器模式),可以实现对集合遍历(集合元素顺序访问).换句话可以这么说,只要实现上面这两个接口 ...
- c#: .net framework 2.0支持扩展方法的办法
c#之扩展方法是个好方法,可惜只在.net framework 3.5及以上版本中用. 2.0版本若用,其编译报错如下: 错误 无法定义新的扩展方法,因为找不到编译器所需的类型“System.Runt ...
随机推荐
- mybatis generator 的日常使用
一.mybatis-generator的基本配置与使用 使用mybatis-generator来生成常用的dao层类与xml,可以满足基础的增删改查功能 1. 添加pom依赖,常用的几个依赖包 < ...
- 如何将 Bitbucket 的 pull request 签出到本地 review
将 pull request 签出到本地进行 review, 最大的好处是可以通过 IDE 来查找各种变量和方法的上下文引用,以便充分发挥我们杠精的本领,将 pull request 中的各种合理和不 ...
- elementUI 级联选择框 表单验证
今天遇到了一个需求:进行级联选择框的表单验证,突然有点懵逼.感觉应该和正常的表单验证类似,但不是很清晰,后来还是在博客园找到了相关参考文章. 先上代码: <el-form :model=&quo ...
- ElasticSearch(四)查询、分词器
正向索引 正排表是以文档的ID为关键字,表中记录文档中每个字的位置信息,查找时扫描表中每个文档中字的信息直到找出所有包含查询关键字的文档. 这种组织方法在建立索引的时候结构比较简单,建立比较方便且易于 ...
- xctf-pwn pwn200
刚看完题目觉得和前面的level3差不多,只是没有给libc而已... 看完大佬的exp之后整个人都不好了.....果然我还是太菜了 32位开了NX sub_8048484,read函数,明显的栈溢出 ...
- JAVA基础(jdk安装和环境变量的配置)
JAVA 1.何为JAVA Java的发展可以归纳如下的几个阶段. (1)第一阶段(完善期):JDK 1.0 ( 1995年推出)一JDK 1.2 (1998年推出,Java更名为Java 2): ( ...
- CSS样式基础2
CSS: 一.常用样式:字体,颜色,背景 二.布局:浮动 定位 标签特性 三.标签盒子模型: 边距 边框 四.动画:旋转 渐变 注意:子标签会继承父标签的样式但不是所有的样式都会被继承. 1.1 ...
- Python之生成器、迭代器
生成器 生成器类似返回值为数组的一个函数,这个函数可以接受参数,可被调用,但只能产生一个值,所以大大节省内存. 生成器表达式的语法非常简单,只需要将列表推导式的中括号改成小括号就可以了 [x+x fo ...
- 挖地雷dp c++
// // Created by Arc on 2020/4/27. // /*题文: * 在一个地图上有n个地窖 * ,每个地窖中没有一定数量的地雷, * 同时给出地窖之间连接的路径, * 并规定路 ...
- ROS 机器人技术 - 广播与接收 TF 坐标
上次我们学习了 TF 的基本概念和如何发布静态的 TF 坐标: ROS 机器人技术 - TF 坐标系统基本概念 ROS 机器人技术 - 静态 TF 坐标帧 这次来总结下如何发布一个自定义的 TF 坐标 ...