前段时间将公司的一个项目从 4.5 升级到了 framework 4.8 ,编码的时候发现 Enumerable 中多了三个扩展方法: Append, Prepend, ToHashSet
,想必玩过jquery的朋友一眼就能看出这三个方法的用途,这篇就和你们一块儿来聊聊这三个方法的底层源码实现,看有没有什么新东西能够挖出来。jquery
看到这个个人第一印象就是 Add
方法, 惋惜在 Enumerable 中并无相似的方法,可能后来程序员在这块的呼声愈来愈高,C#开发团队就弥补了这个遗憾。程序员
接下来我写一个小例子往集合的尾部追加一条数据,以下代码所示:app
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); } }
逻辑仍是很是清晰的,再来看看底层源码是怎么实现的。ide
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 迭代一下,以下图:this
咱们知道集合的添加除了 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 采用的是 头插法,因此最后插入的元素会在队列头部,以下图:code
若是你不信的话,我能够在 vs 调试中给您展现出来。blog
貌似说的有点啰嗦,最后你们观察一下 AppendPrependN<TSource>.MoveNext
的实现就能够了。
说了这么多,我想你应该明白了哈。
本质上来讲 Prepend 和 Append 是一对的,一个是在前面插入,一个是在后面插入,不要想歪了,若是你细心的话,你会发现 Prepend 也是用了这三个类: AppendPrepend1Iterator<TSource>,AppendPrependIterator<TSource>,AppendPrependN<TSource>
以及 单链表 SingleLinkedNode<TSource>
,这个就留给你们本身研究了哈。
我之前在全内存开发中会频繁的用到 HashSet,毕竟它的时间复杂度是 O(1)
,并且在 Enumerable 中早就有了 ToList 和 ToDictionary,凭啥没有 ToHashSet,在之前只能将 source 塞到 HashSet 的构造函数中,如: new HashSet<int>(source)
,想一想也是够奇葩的哈,并且我还想吐糟一下的是竟然到如今尚未 AddRange 批量添加方法,气人哈,接下来用 ILSpy 看一下这个扩展方法是如何实现的。
整体来讲这三个方法仍是很实用的,我相信在后续的版本中 Enumerable 下的扩展方法还会愈来愈多,愈来愈人性化,人生苦短, 我用C#。