文/玄魂
前言
在上一篇文章(Immutable Collections(1)),我簡要說明了不可變集合的基本概念和簡單應用。從本篇博文開始,會探討下幾個典型集合類型的內部實現機制。本篇博客主要探討ImmutableList<T>實現原理。
博文中引用的代碼並非是.NET源碼,而是反編譯得來,不正確之處,還望指教。
2.1 概述
下圖是ImmutableList<T>類型包含的核心字段、屬性(並非全部),以及和其他類型的關系。這張圖是自動生成的,我直接拿過來沒有做什么改動,可能會讓人雲里霧里,下面我做簡要的說明。
處於最頂端的ImmutableList靜態類,是ImmutableList<T>類型的構造者,它或者直接返回ImmutableList<T>的Empty屬性,或者在Empty的基礎上構造ImmutableList<T>實例,比如下面的代碼:
public static ImmutableList<T> Create<T>()
{
return ImmutableList<T>.Empty;
}
public static ImmutableList<T> Create<T>(IEqualityComparer<T> equalityComparer)
{
return ImmutableList<T>.Empty.WithComparer(equalityComparer);
}
public static ImmutableList<T> Create<T>(T item)
{
return ImmutableList<T>.Empty.Add(item);
}
ImmutableList靜態類下面是核心部分——ImmutableList<T>類型。ImmutableList<T>繼承自如下接口:
IImmutableList<T>, IReadOnlyList<T>, IReadOnlyCollection<T>, IList<T>, ICollection<T>, IList, ICollection, IOrderedCollection<T>, IEnumerable<T>, IEnumerable, IImmutableListQueries<T>
其中IImmutableList<T>定義如下:
public interface IImmutableList<T> : IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable
{
IEqualityComparer<T> ValueComparer
{
get;
}
IImmutableList<T> Clear();
bool Contains(T value);
int IndexOf(T value);
IImmutableList<T> Add(T value);
IImmutableList<T> AddRange(IEnumerable<T> items);
IImmutableList<T> Insert(int index, T element);
IImmutableList<T> InsertRange(int index, IEnumerable<T> items);
IImmutableList<T> Remove(T value);
IImmutableList<T> RemoveAll(Predicate<T> match);
IImmutableList<T> RemoveRange(IEnumerable<T> items);
IImmutableList<T> RemoveRange(int index, int count);
IImmutableList<T> RemoveAt(int index);
IImmutableList<T> SetItem(int index, T value);
IImmutableList<T> Replace(T oldValue, T newValue);
IImmutableList<T> WithComparer(IEqualityComparer<T> equalityComparer);
}
IImmutableListQueries<T>定義如下:
internal interface IImmutableListQueries<T>
{
int Count
{
get;
}
ImmutableList<TOutput> ConvertAll<TOutput>(Func<T, TOutput> converter);
void ForEach(Action<T> action);
ImmutableList<T> GetRange(int index, int count);
void CopyTo(T[] array);
void CopyTo(T[] array, int arrayIndex);
void CopyTo(int index, T[] array, int arrayIndex, int count);
bool Exists(Predicate<T> match);
T Find(Predicate<T> match);
ImmutableList<T> FindAll(Predicate<T> match);
int FindIndex(Predicate<T> match);
int FindIndex(int startIndex, Predicate<T> match);
int FindIndex(int startIndex, int count, Predicate<T> match);
T FindLast(Predicate<T> match);
int FindLastIndex(Predicate<T> match);
int FindLastIndex(int startIndex, Predicate<T> match);
int FindLastIndex(int startIndex, int count, Predicate<T> match);
int IndexOf(T item);
int IndexOf(T item, int index);
int IndexOf(T item, int index, int count);
int LastIndexOf(T item);
int LastIndexOf(T item, int index);
int LastIndexOf(T item, int index, int count);
bool TrueForAll(Predicate<T> match);
}
其他接口,是.NET中原有接口,這里就不列舉了。IImmutableList<T> 的核心行為都定義在這兩個接口當中。
SyncRoot是Object類型字段,作為同步鎖對象。
Empty直接返回當前集合的單例對象EmptySingleton。
ImmutableList<T>構造函數如下:
internal ImmutableList()
{
this.root = ImmutableList<T>.Node.EmptyNode;
this.valueComparer = EqualityComparer<T>.Default;
}
private ImmutableList(ImmutableList<T>.Node root, IEqualityComparer<T> valueComparer)
{
root.Freeze();
this.root = root;
this.valueComparer = valueComparer;
}
在構造函數中我們又發現兩個很重要的類型,Node和IEqualityComparer。IEqualityComparer這里就不解釋了,我們重點關注Node,從字面上理解,這是一個表示節點的類,事實上它是ImmutableList<T>的核心,數據的承載和操作都是對Node類的包裝。下面我們來看看Node的廬山真面目。
2.2 Node
Node類繼承自三個接口,
internal sealed class Node : IBinaryTree<T>, IEnumerable<T>, IEnumerable
我們主要關注IBinaryTree<T>,定義如下:
interface IBinaryTree<out T>
{
int Height
{
get;
}
T Value
{
get;
}
IBinaryTree<T> Left
{
get;
}
IBinaryTree<T> Right
{
get;
}
bool IsEmpty
{
get;
}
int Count
{
get;
}
}
接口很清楚,定義了一個二叉樹,但是這棵二叉樹的具體特性但從接口上還無從得知。現在我們再看Node類。
ImmutableList<T>的EmptySingleton就是返回的上圖中最上面的Node類的EmptyNode。EmptyNode定義如下:
internal static readonly Node EmptyNode = new Node();
Node類的Key和Value屬性是同一個值,就是當前節點的值。在代碼中都是以Key為操作對象。下面分析Node的相關行為的時候,會有更清楚的認識。
frozen是一個bool類型的變量,表示是否凍結。凍結可以說是不可變集合的一個關鍵特性,下面也會對此做詳細的分析。
height是以當前節點為根的樹的高度。
this.height = 1 + Math.Max(left.height, right.height);
count以當前節點為根的樹的節點個數。
this.count = 1 + left.count + right.count;
IsEmpty判斷是否有子節點。
public bool IsEmpty
{
get
{
return this.left == null;
}
}
right和left就是左右子樹。
2.3 行為
2.3.1 初始化
我們通過下面的代碼來觀察Node的初始化過程。
static void Main(string[] args)
{
var fruitBasket = ImmutableList.Create<string>();
var ass = fruitBasket.Add("ddd");
}
啟動程序,首先進入ImmutableList.Create<string>()方法。
Create方法直接返回ImmutableList<T>.Empty,Empty屬性直接返回EmptySingleton。

調用EmptySingleton時觸發ImmutableList<T>的初始化。
接下來在構造函數中調用Node.EmptyNode。
在Node類的無參構造函數中只初始化了一個變量:
此時Node.EmptyNode實例的各字段值如下圖所示:
到此為止第一次初始化結束。
現在測試下帶比較器的構造方式,先新建一個TestCompare類:
class TestCompare<T> : IEqualityComparer<T>
{
public bool Equals(T x, T y)
{
return x.Equals(y);
}
public int GetHashCode(T obj)
{
return this.GetHashCode();
}
}
然后更改Main函數中的代碼:
static void Main(string[] args)
{
var fruitBasket = ImmutableList.Create<string>(new TestCompare<string>());
var ass = fruitBasket.Add("ddd");
}
ImmutableList靜態方法仍然在ImmutableList<T>.Empty基礎上調用了WithCompare方法。
WithCompare方法在初始化了valueCompare字段之后調用了構造函數ImmutableList(ImmutableList<T>.Node root, IEqualityComparer<T> valueComparer):
private ImmutableList(ImmutableList<T>.Node root, IEqualityComparer<T> valueComparer)
{
Requires.NotNull<ImmutableList<T>.Node>(root, "root");
Requires.NotNull<IEqualityComparer<T>>(valueComparer, "valueComparer");
root.Freeze();
this.root = root;
this.valueComparer = valueComparer;
}
上面的構造函數,調用了Freeze()方法,
internal void Freeze()
{
if (!this.frozen)
{
this.left.Freeze();
this.right.Freeze();
this.frozen = true;
}
}
這段代碼,實際上是一個遞歸調用,設置每個節點為凍結狀態。
帶初始值的構造函數,實際是調用了Add方法,我將在下一篇博文中單獨分析。
本篇博文到此結束,未完,待續。。。。。。