前段時間將公司的一個項目從 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#。