一:背景

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 下又有新的扩展方法啦,快来一睹为快吧相关推荐

  1. C#3.0新特性 扩展方法

    扩展方法可以使我们为现有的添加方法,现有的类可是是CLB的也可以是自己定义的. 注意事项:   通过this修饰方法第一个参数  方法声明在静态类中  方法通过对象调用 重要注意事项:   扩展方法和 ...

  2. Linq 下的扩展方法太少了,您期待的 MoreLinq 来啦

    一:背景 1. 讲故事 前几天看同事在用 linq 给内存中的两个 model 做左连接,用过的朋友都知道,你一定少不了一个叫做 DefaultIfEmpty 函数,这玩意吧,本来很流畅的 from. ...

  3. (转)[翻译] ASP.NET MVC Tip #1 - 使用扩展方法创建新的HTML Helper

    原文地址:http://weblogs.asp.net/stephenwalther/archive/2008/06/13/asp-net-mvc-tip-1-creating-new-html-he ...

  4. c#扩展方法奇思妙用高级篇四:对扩展进行分组管理

    从系列文章开篇到现在,已经实现的很多扩展了,但过多的扩展会给我们带来很多麻烦,试看下图: 面对这么多"泛滥"的扩展,很多人都会感到很别扭,的确有种"喧宾夺主"的 ...

  5. C#中的扩展方法学习总结

      版权声明:本文由秦元培创作和发表,采用署名(BY)-非商业性使用(NC)-相同方式共享(SA)国际许可协议进行许可,转载请注明作者及出处,本文作者为秦元培,本文标题为C#中的扩展方法学习总结,本文 ...

  6. [C# 基础知识系列]专题十五:全面解析扩展方法

    引言:  C# 3中所有特性的提出都是更好地为Linq服务的, 充分理解这些基础特性后.对于更深层次地去理解Linq的架构方面会更加简单,从而就可以自己去实现一个简单的ORM框架的,对于Linq的学习 ...

  7. C#.Net工作笔记010---c#中的静态扩展方法_可动态给string等_添加共通方法好用

    技术交流QQ群[JAVA,C++,Python,.NET,BigData,AI]:170933152 之前,给list添加排序的扩展方法的时候用过.下面的作用是去掉list中重复的数据. /// &l ...

  8. .NET中那些所谓的新语法之二:匿名类、匿名方法与扩展方法

    开篇:在上一篇中,我们了解了自动属性.隐式类型.自动初始化器等所谓的新语法,这一篇我们继续征程,看看匿名类.匿名方法以及常用的扩展方法.虽然,都是很常见的东西,但是未必我们都明白其中蕴含的奥妙.所以, ...

  9. Linux下php添加新扩展

    今天干了一件很严重的错事,用TP开发完新项目后,传到服务器上报错,网上查了一下,说未加载PDO,就加载吧.找到配置文件PHP.ini,搜到;extension=php_pdo_mysql.dll,去掉 ...

最新文章

  1. jquery插件制作
  2. python自学网站 知乎-如何自学Python拿到25K的薪资?非常感谢这11个站点!
  3. POJ 1556 The Doors (未完)
  4. 类的成员函数与内联以及静态成员
  5. superMap添加marker及连线
  6. Android 开发 存储目录的详解
  7. [css] word-wrap、word-break和white-space有什么区别?
  8. Python_大众点评网站数据爬虫
  9. 良好的XHTML编写习惯
  10. 【debian】解决debian中文安装后出现乱码的问题
  11. android.os.DeadObjectException的解决办法
  12. Java语言程序设计(基础篇)课后答案
  13. NR R15中的TypeII CSI-Codebook量化反馈
  14. python3修改文件的编码格式_python批量修改文件编码格式的方法
  15. haosou属于搜索引擎的_中国的搜索引擎有哪些?
  16. 问道手游服务器修改密码,问道手游去哪改密码 问道手游怎么更换修改密码手机号...
  17. SpringBoot优缺点分析
  18. staruml 依赖于 libgcrypt11 (= 1.4.5);然而:未安装软件包 libgcrypt11。
  19. CRM学习笔记类转换工具(pojo互转)上下文中获取用户名cookie工具
  20. 炎炎夏日,快用代码下场雨

热门文章

  1. iOS笔记之UIKit_UINavigationController
  2. 微软文本检索_如何在Microsoft Word中引用其他文档中的文本
  3. 使用MyQ打开车库门时如何接收警报
  4. 最大连续子数组和与JUnit测试
  5. nodejs即时聊天
  6. 设置utf8编码问题
  7. WPF中设置了WindowStyle=None后,窗口仍然有边框的解决方法
  8. Java正则表达式获取网页所有网址和链接文字
  9. 复制Oracle表的结构
  10. Oracle 10R2 研究--db_file_multiblock_read_count对成本的影响