NET 2.0 基礎類庫中的範型

NET 2.0 基礎類庫中的範型——範型集合
www.chinacs.net  2004-08-10  中文C#技術站
範型集合
毫無疑問,範型最典型的應用莫過於範型集合了。在 .NET 2.0 中提供了已有集合類和接口的範型版本,它們位於 System.Collections.Generic 命名空間中。
.NET 2.0 中新的範型集合類並不是簡單的在已有非範型集合類的設計上多加了個範型參數 T 而已。新的範型集合類的設計充分吸收了已有設計中的合理之處並摒棄了一些不甚合理之處,同時引入了新的針對範型的設計。所以,新的範型類和接口的設計應該更加合理和有效,不過 .NET 程序員則需要花些時間學習新的設計並瞭解與已有設計有什麼樣的不同,以及在將代碼從非範型集合移植到範型集合時可能會出現的兼容性問題。
下面是範型集合和已有非範型集合的對照表(不全):
 
 
非範型接口
範型接口
非範型類
範型
IEnumerator
IEnumerator<T>
ArrayList
List<T>
IEnumerable
IEnumerable<T>
Stack
Stack<T>
ICollection
ICollection<T>
Queue
Queue<T>
IList
IList<T>
DictionaryEntry
KeyValuePair<K, V>
IDictionary
IDictionary<T>
Hashtable
Dictionary<K, V>
IComparable
IComparable<T>
Comparer
Comparer<T>
IComparer
IComparer<T>
 
 
 
 
可以看到,部分類的名字做了修改,例如 ArrayList 現在改爲 List<T>Hashtable 改爲 Dictionary<K, V>DictionaryEntry 改爲 KeyValuePair<K, V> 等等。這樣的命名當然更加合理(因爲 IList<T> 是接口,List<T> 是對應的具體類;同樣 IDictionary<K, V> 是接口, Dictionary<K, V> 是對應的具體類;而 KeyValuePair<K, V> 顯然比 DictionaryEntry 更加容易理解和記憶),但對已經習慣了以前的命名的程序員來說可能一開始會有點找不找北的感覺。
前面說過,新的範型集合接口/類和以前的非範型版本相比有較大的設計改變,下面我們來看看這些變化。
IEnumerator<T>
IEnumerator/IEnumerator<T> 接口允許對一個集合進行遍歷,主要用在 .NET 編程語言的遍歷語句中,例如 C# foreach 語句。用戶代碼通常不直接使用這個接口。IEnumerator<T> 和非範型版本 IEnumerator 相比去掉了 Reset 方法。這可能是出於以下考慮:
l IEnumerator<T> 接口主要設計用於支持諸如 foreach 這樣的語句,而這些地方用不到 Reset 方法。去掉 Reset 方法使得設計更加簡化並降低了實現該接口的難度。如果調用者需要類似 Reset 的功能,可以重新獲取一個枚舉器(例如通過調用 GetEnumerator 方法)。
l C# 2.0 Iterators 提供了自動生成枚舉器的方法,編譯器自動爲指定的類實現 IEnumerator 接口和 IEnumerator<T> 接口。而對IEnumerator 接口的 Reset 方法的實現只是簡單的拋出 System.NotSupportedException 異常。所以在 IEnumerator<T> 的設計中移去 Reset 方法顯得非常自然和合理。
ICollection<T>
ICollection<T> 接口的設計和非範型版本 ICollection 相比改變很大。ICollection 接口的最初設計意圖是支持複製集合元素(通過 Count 屬性和 CopyTo 方法),以及支持同步訪問模式(通過 IsSynchronized 屬性和 SyncRoot 屬性)。ICollection<T> 的設計保留了對複製集合元素的支持,但是摒棄了對同步訪問模式的支持,這是因爲實踐證明 ICollection 的同步訪問模式是讓人困惑和低效的。不少剛學 .NET 的程序員一開始搞不懂 SyncRoot 是個什麼東東,有什麼用。另外,從性能和邏輯上考慮,何時鎖定集合應該由調用者決定,而不是由實現者決定。所以總的來說 IsSynchronized SyncRoot 不是很理想的設計。因此,ICollection<T> 沒有 IsSynchronized 屬性和 SyncRoot 屬性。
除此之外,ICollection<T> 還增加了一些新的屬性和方法,它們讓 ICollection<T> 接口變得更加有用。這些屬性和方法事實上是從 IList IDictionary 的共同屬性和方法移植過來的,包括:
l IsReadOnly,用於判斷集合是否是隻讀的。
l Add/Remove/Clear,用於對集合元素進行管理。這些方法對列表和字典都是有效的。
l Contains,用於判斷集合中是否包含指定的值。
另外,對於一些不需要更改集合的使用情景來說,提供一個類似 IReadOnlyCollection<T> 這樣的接口可能會有意義,它只需要支持 Count 屬性,CopyTo 方法和 Contains 方法即可。然而微軟並沒有採用這樣的設計,主要理由是爲了使基本集合接口儘量簡單和易用。微軟的建議是程序員需要的話自己定義這樣的接口。
IList<T> List<T>
剛纔提到,IList<T> 相對於 IList 的變化是通用的屬性和方法被移植入 ICollection<T> 了,僅剩下對列表有效的基於索引訪問的屬性和方法。
List<T> 相對 ArrayList 來講也做了很大的設計改變。做出這些改變的主要考慮是性能,因爲動態數組是 .NET 程序使用的最基本的數據結構之一,它的性能影響到應用程序的全局。例如,以前 ArrayList 默認的 Capacity 16,而 List<T> 的默認 Capacity 4,這樣可以儘量減小應用程序的工作集。另外,List<T> 的方法不是虛擬方法(ArrayList 的方法是虛擬方法),這樣可以利用函數內聯來提高性能(虛函數不可以被內聯)。List<T> 也不支持問題多多的 Synchronized 同步訪問模式。
List<T> 相比 ArrayList 增加的一個重要特性則是對 Functional Programming 的支持。我們將在 Functional Programming 部分介紹這一新特性。
IDictionary<K, V> Dictionary<K, V>
IDictionary<K, V> Dictionary<K, V> 相比非範型版本一個很大的變化是當指定的鍵不存在時索引器的處理邏輯。對 IDictionary Hashtable 來說,值的存儲類型是 object,當鍵不存在時,索引器將返回 null,當鍵存在而對應值爲 null 的話也返回 null(設計者可能認爲調用者通常關心的是值是不是有效,而不是區分這兩種情況)。然而,對於範型版本來說,因爲存儲的可能是值類型,所以不可以返回 null 來作爲鍵不存在的標識。因此, IDictionary<K, V>Dictionary<K, V> 的索引器在指定鍵不存在的情況下將拋出 KeyNotFoundException 異常。這將導致源代碼級別的不兼容,也就是說,以下的代碼在存儲值類型的情況下將不可移植,而必須改寫(例如先使用 ContainsKey 方法判斷指定鍵是否存在,然後再訪問;或者使用不拋出異常的 TryGetValue 方法):
 
 
Hashtable map = ...;
if (map[“s1”] == null) { // 如果是範型版本將拋出異常而不是返回null
...
}
 
 
這一問題反映了設計者在最初設計 Hashtable 類的時候考慮的並不是很周到——使用了魔術值 null,既可以是指鍵不存在的情況,也可以是鍵存在而值爲 null 的情況,而這一點對範型是不成立的。另外,從 Design By Contract 的角度講,當指定鍵值不存在時,拋出異常是很自然的事情(與是否使用範型無關),就像數組越界一樣。估計原設計者主要是從性能角度考慮才使用了 null 而不是異常處理。
IComparable<T>IComparer<T> Comparer<T>
這幾個接口/類用於比較和排序。IComparable<T> 相比 IComparable 添加了 Equals 方法,當然也是爲了儘量減少 boxing(對於值類型類說)。IComparer<T> 相對 IComparer 則不僅添加了 Equals 方法,而且還新增加了 GetHashCode 方法。咋看一下似乎和比較不太相關,但事實上,對於字符串來說,比較和哈希值是息息相關的。在以前的非範型設計中,需要同時使用 IComparer IHashCodeProvider 兩個接口,例如 Hashtable 的構造函數:
 
 
public Hashtable(IHashCodeProvider hcp, IComparer comparer);
 
 
其中 IHashCodeProvider IComparer 兩個參數必須匹配(例如都使用 InvariantCultureIgnoreCase),否則結果會不正確。爲了讓程序員能夠快速的編寫出正確的代碼,現在的 IComparer<T> 把比較和生成哈希代碼的功能集成在一起,例如 Dictionary<K, V> 的構造函數:
 
 
public Dictionary(IComparer<K, V> comparer);
 
 
Comparer<K, V> 提供了 IComparer<K, V> 的默認實現,微軟最新的設計指南建議使用 Comparer<K, V> 而不是其他方法來比較和排序。
另外,.NET 2.0 中新添加了一個字符串比較類——StringComparer,位於 System 命名空間。StringComparer 不是一個範型類,不過它實現了 IComparer<string> 接口,對於需要提供大小寫無關的字符串比較很有用。例如,下面的代碼創建了一個大小寫無關的字典:
 
 
Dictionary<string, int> dict = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);
dict[“Test”] = 10;
int n = dict[“test”];
 
發佈了19 篇原創文章 · 獲贊 6 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章