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