.NET不可變集合已經正式發布
英文原文:.NET Immutable Collections Ready for Production
微軟基礎類庫(Base Class Library)團隊已經完成了.NET 不可變集合的正式版本,但不包括 ImmutableArray。與其一起發布的還包括針對其它不可變對象類型的設計指南。
如果你需要在多個線程中安全地共享集合,并且允許每個線程在需要時對其內容進行改變。這種場景就是不可變集合所設計的初衷。只讀集合在使用時需要復制集合中的全部內容,而新的不可變集合可以以一種更高性能的方式從一個現有集合中進行創建。
使用不可變集合需要特別當心,因為你很容易錯誤地寫成“list.Add (item)”,而正確的方法是“list = list.Add (item)”。甚至編譯器也可能產生類似的錯誤,這也是為什么不可變集合不支持構造函數的原因。考慮以下代碼:
list = new ImmutableList<int> {1, 2, 3};
在編譯后會產生以下代碼:
temp = new ImmutableList (); temp.Add (1); temp.Add (2) temp.Add (3) list = temp;
由于 3 次 Add 方法的結果都被丟棄,最終整個集合包含的項數目為0,而不是期望中的3。
不可變對象指南
Immo Lendwerth 建議,當你在創建自己的不可變對象時,在其中加入適當的 WithXxx 方法。對簡單的對象來說,為每一個屬性創建一個 WithXxx 方法即可。當屬性值需要變化時,該方法會返回當前對象的一個拷貝。
如果某屬性代表了一個結合,那么這種模式就需要一點變化。以下這段代碼來自 Immo 的發布聲明:
class Order { public Order (IEnumerable<OrderLine> lines) { Lines = lines.ToImmutableList (); } public ImmutableList<OrderLine> Lines { get; private set; } public Order WithLines (IEnumerableOrderLine> value) { return Object.ReferenceEquals (Lines, value) ? this : new Order (value); } }
如你所見,WithLines 方法可接受任意 IEnumerable。因此你可以傳遞一個新創建的 ImmutableList 對象,或者是某個 LINQ 表達式的結果。這種方式已經足以滿足需求了,不過他還建議提供某些輔助方法:
class Order { //... public Order AddLine (OrderLine value) { return WithLines (Lines.Add (value)); } public Order RemoveLine (OrderLine value) { return WithLines (Lines.Remove (value)); } public Order ReplaceLine (OrderLine oldValue, OrderLine newValue) { return oldValue == newValue ? this : WithLines (Lines.Replace (oldValue, newValue)); } }
ImmutableArray 被移除
由于性能方面的原因,ImmutableArray 從最終的發布版本中被移除。其原因是:為了滿足內存性能指標,ImmutableArray 必須設計成一個值對象,并且為了保持值對象的語義,ImmutableArray 的默認實例必須表現為一個空數組形式。不幸的是,為了達到這一點,對空值的檢測(null check)會使得 C# 無法移除對數組邊界的檢測,而這一點是為達到良好 CPU 性能的一個重要考慮事項。
由于 ImmutableArray 類對于 Roslyn 編譯器項目非常重要,設計者曾考慮刪除會導致性能問題的空值檢測功能,但又因此產生了另外的問題,Immo 這樣寫道:
由于所有的值類型都有一個自動產生的默認構造函數,它會將該值類型初始化為它的默認狀態,而 ImmutableArray<T>的默認值是空,它的底層數組實現則為 null。因此,AddRange 方法的實現會因為 NullReferenceException 的產生而崩潰。
這一問題還表現在其它一些地方,由于 ImmutableArray<T>實現了某些集合接口(例如 IEnumerable 和 IReadOnlyList),因此你可以把它傳遞給某些接受這種接口的方法。由于這種接口引用是非空的,使用者在調用它的方法或者屬性時不會考慮到有可能產生 NullReferenceException。
基礎類庫團隊并未放棄這個項目,他們還在研究其它設計方式,以爭取讓 ImmutableArray 重新亮相。