泛型(generic)是CLR和編程語言提供一種特殊機(jī)制,它支持另一種形式的代碼重用,即"算法重用"。
簡單地說,開發(fā)人員先定義好一個(gè)算法,比如排序、搜索、交換等。但是定義算法的開發(fā)人員并不設(shè)定該算法要操作什么數(shù)據(jù)類型;該算法可廣泛地應(yīng)用于不同類型的對(duì)象。然后,另一個(gè)開發(fā)人員只要指定了算法要操作的具體數(shù)據(jù)類型,就可以使用這個(gè)現(xiàn)成的算法了。
泛型有兩種表現(xiàn)形式:泛型類型和泛型方法。
泛型類型:大多數(shù)算法都封裝在一個(gè)類型中,CLR允許創(chuàng)建泛型引用類型和泛型值類型,但不允許創(chuàng)建泛型枚舉類型。除此之外,CLR還允許創(chuàng)建泛型接口和泛型委托。
泛型方法:方法偶爾也封裝有用的算法,所以CLR允許引用類型、值類型或接口中定義泛型方法。
兩者都是表示API的基本方法(不管是指一個(gè)泛型方法還是一個(gè)完整的泛型類型),以致平時(shí)期望出現(xiàn)一個(gè)普通類型的地方出現(xiàn)一個(gè)類型參數(shù)。比如,List<T>,在類名之后添加一個(gè)<T>,表明它操作的是一個(gè)未指定的數(shù)據(jù)類型。定義泛型類型和方法時(shí),它為類型指定的任何變量(比如 T)都稱為類型參數(shù)(type parameter)。T代表一個(gè)變量名,在源代碼中能夠使用一個(gè)數(shù)據(jù)類型的任何位置 ,都能使用T。
類型參數(shù)是真實(shí)類型的占位符。在泛型聲明中,類型參數(shù)要放在一堆尖括號(hào)內(nèi),并以逗號(hào)分隔。所以,在Dictionary<TKey, TValue>中,類型參數(shù)是TKey和TValue。使用泛型類型或方法時(shí),要使用真實(shí)的類型代替。這些真實(shí)的類型稱為類型實(shí)參(type argument)。
泛型為開發(fā)人員提供了以下優(yōu)勢:
1)源代碼保護(hù) 使用一個(gè)泛型算法的開發(fā)人員不需要訪問算法的源代碼。然而,使用C++模板的泛型技術(shù)時(shí),算法的源代碼必須提供給準(zhǔn)備使用算法的用戶。
2)類型安全 將一個(gè)泛型算法應(yīng)用于一個(gè)具體的類型時(shí),編譯器和CLR能理解開發(fā)人員的意圖,并保證只有與制定數(shù)據(jù)類型兼容的對(duì)象才能隨同算法使用。
3)更清晰的代碼 由于編譯器強(qiáng)制類型安全性,所以減少了源代碼中必須進(jìn)行的轉(zhuǎn)型次數(shù)。
4)更佳的性能 在有泛型之前,要想定義一個(gè)常規(guī)化的算法,它的所有成員都要定義成操作Object數(shù)據(jù)類型。這其中就要有裝箱和拆箱之間的性能損失。由于現(xiàn)在能創(chuàng)建一個(gè)泛型算法來操作一個(gè)具體的值類型,所以值類型的實(shí)例能以傳值的方式傳遞,CLR不再需要只需任何裝箱操作。由于不再需要轉(zhuǎn)型,所以CLR不必檢查嘗試一次轉(zhuǎn)型操作是否類型安全,同樣提高了代碼的允許速度。
一、 Framework類庫中的泛型
泛型最明顯的應(yīng)用就是集合類。FCL已經(jīng)定義了幾個(gè)泛型集合類。其中大多數(shù)類能在Sysytem.Collections.Generic和System.Collections.ObjectModel命名空間中。要使用線程安全的泛型集合類,可以去System.Collections.Concurrent命名空間尋找。 Microsoft建議開發(fā)人員使用泛型集合類,并基于幾個(gè)方面的原因,不鼓勵(lì)使用非泛型集合類。首先,非泛型無法獲得類型安全性、更清晰的代碼和更佳的性能。其次,泛型具有更好的對(duì)象模型。 集合類實(shí)現(xiàn)了許多接口,放入集合中的對(duì)象也可能實(shí)現(xiàn)了接口,集合類可利用這些接口執(zhí)行像排序這樣的操作。FCL內(nèi)建了許多泛型接口定義,所以在使用接口時(shí),也能體會(huì)到泛型帶來的好處。常用的接口包含在Sysytem.Collections.Generic命名空間中。 新的泛型接口并不是設(shè)計(jì)用來完全取代非泛型接口。 System.Array類(即所有數(shù)組的基類)提供了大量靜態(tài)泛型方法,比如,AsReadonly、FindAll、Find、FindIndex等。二、Wintellect的Power Collections庫 Power Collections庫由Wintellect制作,這個(gè)庫有一系列集合類構(gòu)成,任何人都可以免費(fèi)下載和使用。
| 集合類名稱 | 說明 |
| BigList<T> | 有序T對(duì)象集合。操作100個(gè)以上的數(shù)據(jù)項(xiàng)是,效率非常高 |
| Bag<T> | 無序T對(duì)象的集合,集合進(jìn)行了哈希處理,并允許重復(fù)項(xiàng) |
| OrderedBag<T> | 有序T對(duì)象的集合,允許重復(fù)值 |
| Set<T> | 無序T數(shù)據(jù)項(xiàng)集合,不允許重復(fù)項(xiàng)。添加重復(fù)項(xiàng)后,會(huì)只保留一個(gè) |
| OrderedSet<T> | 有序T數(shù)據(jù)項(xiàng)的集合,不允許重復(fù)項(xiàng) |
| Deque<T> | 雙端隊(duì)列(double-ending queue)。類似于一個(gè)列表,但在起始處添加/刪除數(shù)據(jù)項(xiàng)時(shí),比列表更高效 |
| OrderedDictionary<TKey,TValue> | 字典,其中的鍵進(jìn)行了排序,每個(gè)鍵都有一個(gè)對(duì)應(yīng)的值 |
| MultiDictionary<TKey,TValue> | 字典,其中每個(gè)鍵都可以有多個(gè)值,對(duì)鍵進(jìn)行了哈希處理,允許重復(fù),而且數(shù)據(jù)項(xiàng)是無序的 |
| OrderedMultiDictionary<TKey,TValue> | 字典,其中的鍵進(jìn)行了排序,每個(gè)鍵都可以有多個(gè)值(同樣進(jìn)行了排序)。允許重復(fù)的鍵 |
三、泛型的基礎(chǔ)結(jié)構(gòu)
為了是泛型能夠工作,Microsoft必須完成以下工作:
1)創(chuàng)建新的IL指令,使之能夠識(shí)別類型實(shí)參 2)修改現(xiàn)有元數(shù)據(jù)表的格式,以便表示具有泛型參數(shù)的類型名稱和方法 3)修改各種編程語言(C#等),以支持新的語法,允許開發(fā)人員定義個(gè)引入泛型類型和方法 4)修改編譯器,使之能生成新的IL指令和修改元數(shù)據(jù)格式 5)修改JIT編譯器,使之能夠處理新的、支持類型實(shí)參的IL指令,以便生成正確的本地代碼 6)創(chuàng)建新的反射成員,使開發(fā)人員能查詢類型和成員,以判斷它們是否具有泛型參數(shù)。另外,還必須定義新的反射成員,使開發(fā)人員能在運(yùn)行時(shí)創(chuàng)建泛型類型和方法定義。 7)修改調(diào)試器以以顯示和操作泛型類型、成員、字段以及局部變量。 8)修改VisualStudio 的"智能感知"(IntelliSense)特性。 1.開放類型和封閉類型 前面我們討論過CLR如何為應(yīng)用程序的每個(gè)類型創(chuàng)建一個(gè)內(nèi)部數(shù)據(jù)結(jié)構(gòu),這種數(shù)據(jù)結(jié)構(gòu)稱為類型對(duì)象。 具有泛型類型參數(shù)的類型仍然是類型,CLR同樣會(huì)為它創(chuàng)建一個(gè)內(nèi)部類型對(duì)象。無論是引用類型(類)、值類型(結(jié)構(gòu))、接口類型,還是委托類型,這一點(diǎn)都是成立的。 如果沒有為任何類型參數(shù)提供類型實(shí)參,聲明的就是一個(gè)未綁定泛型類型。 如果指定了類型實(shí)參,該類型就稱為已構(gòu)造類型。 我們知道,類型可以看做是對(duì)象的藍(lán)圖。同樣的,未綁定泛型類型是已構(gòu)造類型的藍(lán)圖。它是一種額外的抽象層。 已構(gòu)造類型可以是開放類型或封閉類型。 "開放類型"(open type)是指還包含一個(gè)類型參數(shù),CLR禁止構(gòu)造開放類型的任何實(shí)例。這一點(diǎn)類似于CLR禁止構(gòu)造接口類型的實(shí)例。 代碼引用一個(gè)泛型類型時(shí),可指定一組泛型類型實(shí)參。假如為所有類型實(shí)參傳遞的都是實(shí)際數(shù)據(jù)類型,類型就稱為"封閉類型"(closed type)。也就是說,具有泛型"類型實(shí)參"的類型稱為"封閉類型"。CLR允許構(gòu)造封閉類型的實(shí)例。 當(dāng)代碼引用一個(gè)泛型類型時(shí),可能會(huì)留下一些泛型類型實(shí)參未指定。這會(huì)在CLR中創(chuàng)建一個(gè)新的開放類型的對(duì)象,而且不能創(chuàng)建該類型的實(shí)例。比如:internal static class PRogram { private static void Main(string[] args) { Object o = null; // Dictionary<,> 是一個(gè)開放類型,有兩個(gè)類型參數(shù) Type t = typeof(Dictionary<,>); // 嘗試創(chuàng)建該類型的一個(gè)實(shí)例 (失敗) o = CreateInstance(t); Console.WriteLine(); // DictionaryStringKey<> 是一個(gè)開放類型,有一個(gè)類型參數(shù) t = typeof(DictionaryStringKey<>); // 嘗試創(chuàng)建該類型的一個(gè)實(shí)例 (失敗) o = CreateInstance(t); Console.WriteLine(); // DictionaryStringKey<Guid> 是一個(gè)封閉類型 t = typeof(DictionaryStringKey<Guid>); // 嘗試創(chuàng)建該類型的一個(gè)實(shí)例 (成功) o = CreateInstance(t); // Prove it actually worked Console.WriteLine("Object type=" + o.GetType()); Console.ReadKey(); } private static Object CreateInstance(Type t) { Object o = null; try { o = Activator.CreateInstance(t); Console.Write("已創(chuàng)建 {0} 的實(shí)例", t.ToString()); } catch (ArgumentException e) { Console.WriteLine(e.Message); } return o; } // A partially specified open type internal sealed class DictionaryStringKey<TValue> : Dictionary<String, TValue> { } }
最后顯示地結(jié)果為:

可以看出,Activator的CreateInstance方法會(huì)在構(gòu)造開發(fā)類型的實(shí)例時(shí)拋出一個(gè)ArgumentException異常。注意,在異常的字符串消息中,指明類型中仍然含有一些泛型參數(shù)。 從輸出結(jié)果可以看出,類型名是以一個(gè)"`"字符和一個(gè)數(shù)字結(jié)尾的。這個(gè)數(shù)字代表類型的元數(shù),也就是類型要求的類型參數(shù)的個(gè)數(shù)。例如,Dictionary類的元數(shù)為2,它要求為TKey和TValue這兩個(gè)類型參數(shù)指定具體類型。 還要注意的是,CLR會(huì)在類型對(duì)象內(nèi)部分配類型的靜態(tài)字段。因此,每個(gè)封閉類型都有自己的靜態(tài)字段。換言之,假如List<T>定義了任何靜態(tài)字段,這些字段不會(huì)在一個(gè)List<DataTime>和List<String>之間共享;每個(gè)封閉類型對(duì)象都有它自己的靜態(tài)字段。另外,假如一個(gè)泛型類型定義了一個(gè)靜態(tài)構(gòu)造器,那么針對(duì)每個(gè)封閉類型,這個(gè)構(gòu)造器都會(huì)執(zhí)行一次。在泛型類型上定義一個(gè)靜態(tài)構(gòu)造器的目的是保證傳遞的類型參數(shù)滿足特定的條件。例如,如果希望一個(gè)泛型類型值用于處理枚舉類型,可以如下定義:internal sealed calss GenericTypeThatReqiresAnEnum<T> { static GenericTypeThatReqiresAnEnum() { if ( !typeof (T).IsEnum) { throw new ArgumentException("T must be an enumerated type") } }}CLR提供了一個(gè)名為"約束"(constraint)的功能,可利用它更好地定義一個(gè)泛型類型來指出哪個(gè)類型實(shí)參是有效的。
2.泛型類型和繼承 泛型類型仍然是類型,所以它能從其他任何類型派生。使用一個(gè)泛型類型并指定類型實(shí)參時(shí),實(shí)際上是在CLR中定義一個(gè)新的類型對(duì)象,新的類型對(duì)象是從派生該泛型類型的那個(gè)類型派生的。也就是說,由于List<T>是從Object派生的,那么List<String>和List<Guid>也是從Object派生的。 3. 泛型類型同一性 有的時(shí)候,泛型語法會(huì)將開發(fā)人員搞糊涂,所以有的開發(fā)人員定義了一個(gè)新的非泛型類類型,它從一個(gè)泛型類型派生,并指定了所有的類型實(shí)參。例如,為了簡化一下代碼:List<DateTime> dt = new List<DateTime>();
一些開發(fā)人員可能首先定義下面這樣的一個(gè)類:
internal sealed class DateTimeList : List<DataTime> { //這里無需放任何代碼!}然后就可以進(jìn)一步簡化創(chuàng)建:
DateTimeList dt = new DateTimeList ();
這樣做表面上是方便了,但是決定不要單純處于增強(qiáng)源代碼的易讀性類這樣定義一個(gè)新類。這樣會(huì)喪失類型同一性(identity)和相等性(equivalence)。如下:
Boolean sameType = (typeof(List<DateTime>) == (typeof(DateTimeList));
上述代碼運(yùn)行時(shí),sameType會(huì)初始化為false,因?yàn)楸容^的是兩個(gè)不同類型的對(duì)象。也就是說,假如一個(gè)方法的原型接受一個(gè)DateTimeList,那么不能將一個(gè)List<DateTime>傳給它。然而,如果方法的原型接受一個(gè)List<DateTime>,那么可以將一個(gè)DateTimeList傳給它,因?yàn)镈ateTimeList是從List<DateTime>派生的。
C#提供一種方式,允許使用簡化的語法來引用一個(gè)泛型封閉類型,同時(shí)不會(huì)影響類的相等性——使用using指令。比如:using DateTimeList = System.Collections.Generic.List<System.DateTime>;
現(xiàn)在只想下面這行代碼時(shí),sameType會(huì)初始化為true:
Boolean sameType = (type(List<DateTime>) == (ypeof(DateTimeList));
還有,可以使用C#的隱式類型局部變量功能,讓編譯器根據(jù)表達(dá)式的類型來推斷一個(gè)方法的局部變量的類型。
4.代碼爆炸 使用泛型類型參數(shù)的一個(gè)方法在進(jìn)行JIT編譯時(shí),CLR獲取方法的IL,用指定的類型實(shí)參進(jìn)行替換,然后創(chuàng)建恰當(dāng)?shù)谋镜卮a。然而,這樣做有一個(gè)缺點(diǎn):CLR要為每種不同的方法/類型組合生成本地代碼。我們將這個(gè)現(xiàn)象稱為"代碼爆炸"。它可能造成引用程序集的顯著增大,從而影響性能。 CLR內(nèi)建了一些優(yōu)化措施,能緩解代碼爆炸。首先,假如為一個(gè)特定的類型實(shí)參調(diào)用了一個(gè)方法,以后再次使用相同的類型實(shí)參來調(diào)用這個(gè)方法,CLR只會(huì)為這個(gè)方法/類型組合編譯一次。所以,如果一個(gè)程序集使用List<DateTime>,一個(gè)完全不同的程序集也使用List<DateTime>,CLR只會(huì)為List<DateTime>編譯一次方法。 CLR還提供了一個(gè)優(yōu)化措施,它認(rèn)為所有引用類型實(shí)參都是完全相同的,所以代碼能夠共享。之所以能這樣,是因?yàn)樗幸妙愋偷膶?shí)參或變量時(shí)間只是執(zhí)行堆上的對(duì)象的指針,而對(duì)象指針全部是以相同的方式操作的。 但是,假如某個(gè)類型實(shí)參是值類型,CLR就必須專門為那個(gè)值類型生新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注