程序= 算法 + 數據。編程人員設計好一種算法,例如排序、比較、交換等等。這些算法應該應用於不一樣的數據類型,而不是爲每一個數據類型都寫一個專有的算法。算法
CLR 容許建立:泛型引用類型,泛型值類型,泛型接口,泛型委託。編程
CLR 不容許建立:泛型枚舉類型。數組
在FCL 中泛型最明顯的應用就是集合類,FLC 在 System.Collections.Generic 和 System.Collections.ObjectModel 命名空間中提供了多個泛型集合類。System.Collections.Concurrent 命名空間則提供了線程安全的泛型集合類。安全
開放類型和封閉類型less
具備泛型類型參數的類型任然是類型,CLR 一樣會爲它建立內部的類型對象。這一點適合引用類型(類)、值類型(結構)、接口類型和委託類型。具備泛型類型的參數類型稱爲開放類型,CLR 禁止構造開放類型的任何實例。這相似於CLR 禁止構造接口類型的實例。性能
代碼引用泛型類型時可指定一組泛型類型實參。爲全部類型參數都傳遞了實際的數據類型,類型就成了封閉類型。CLR容許構造封閉類型的實例。ui
using System; using System.Collections.Generic; // A partially specified open type internal sealed class DictionaryStringKey<TValue> : Dictionary<String, TValue> { } public static class Program { public static void Main() { Object o = null; // Dictionary<,> is an open type having 2 type parameters Type t = typeof(Dictionary<,>); // Try to create an instance of this type (fails) o = CreateInstance(t); Console.WriteLine(); // DictionaryStringKey<> is an open type having 1 type parameter t = typeof(DictionaryStringKey<>); // Try to create an instance of this type (fails) o = CreateInstance(t); Console.WriteLine(); // DictionaryStringKey<Guid> is a closed type t = typeof(DictionaryStringKey<Guid>); // Try to create an instance of this type (succeeds) o = CreateInstance(t); // Prove it actually worked Console.WriteLine("Object type=" + o.GetType()); } private static Object CreateInstance(Type t) {CHAPTER 12 Generics 273 Object o = null; try { o = Activator.CreateInstance(t); Console.Write("Created instance of {0}", t.ToString()); } catch (ArgumentException e) { Console.WriteLine(e.Message); } return o; } }
編譯並容許上述代碼獲得下面的結果:this
Cannot create an instance of System.Collections.Generic.
Dictionary`2[TKey,TValue] because Type.ContainsGenericParameters is true.
Cannot create an instance of DictionaryStringKey`1[TValue] because
Type.ContainsGenericParameters is true.
Created instance of DictionaryStringKey`1[System.Guid]
Object type=DictionaryStringKey`1[System.Guid]
泛型類型和繼承atom
泛型類型任然是類型,因此能從其餘任何類型派生。使用泛型類型並指定類型參數時,實際是在CLR 中定義了一個新的類型對象,這個新的類型對象從泛型類型派生自的哪一個類型派生。例如: List<T> 從 Object 派生,因此List<String> 和 List<Giud> 也從Object 派生。指定類型實參不影響繼承層次結構,理解這一點,有助於你判斷哪些強制類型轉換是容許的,哪些不容許。spa
泛型類型同一性
同一性就是爲了方便使用泛型類型,能夠像C++中的宏定義那樣將一個泛型類型用其餘的符號表明。C# 容許使用簡化的語法來引用泛型封閉類型,同時不會影響類型的相等性。這個語法要求在源文件頂部使用傳統的using 指令,例如:
using DateTimeList = System.Collections.Generic.List<System.DateTime>;
執行下面的代碼驗證一下:
Boolean sameType = (typeof(List<DateTime>) == typeof(DateTimeList));
代碼爆炸
使用泛型類型參數的方法在進行JIT 編譯時,CLR 獲取方法的IL,用指定的類型實參替換,而後建立恰當的本機代碼(這些代碼是爲操做指定數據類型「量身定製的」),這正是泛型的重要特色。但這樣作也有一個缺點: CLR 要爲每種不一樣的方法/類型 組合生成本機代碼。咱們將這個現象稱爲代碼爆炸。它可能會形成應用程序的工做集顯著增大,從而損害性能。
CLR 內建了一些措施能緩解代碼爆炸。
一、一次編譯,重複使用。爲特定的類型實參調用了一個方法後,之後再調用相同的類型實參的方法時,CLR只會在第一次編譯代碼。
二、CLR 認爲全部引用類型實參都徹底相同,因此代碼可以共享。對於任何引用類型的實參,都會調相同的代碼。
可是假如某個類型實參是值類型,CLR 就必須專門爲哪一個值類型生成本機代碼。
泛型接口
若是沒有泛型接口,每次用非泛型接口(如 IComparable)來操縱值類型都會發生裝箱,並且會失去編譯時的類型安全性。所以,CLR 提供了對泛型接口的支持。引用類型或值類型可指定類型實參實現泛型接口。也可保持類型實參的未指定狀態來實現泛型接口。
泛型委託
CLR 支持泛型委託,目的是保證任何類型的對象都能以類型安全的方式傳給回調方法。泛型委託容許值類型實例在傳給回調方法時不進行任何裝箱。
委託和接口的逆變和協變泛型類型實參
委託的每一個泛型類類型參數均可以標記爲協變量或逆變量。利用這個功能,可將泛型委託類型的變量轉換爲相同的委託類型(但泛型參數類型不一樣)。泛型類型參數能夠是如下任何一種形式。
不變量,意味着泛型類型參數不能改變。
逆變量,意味着泛型類型參數能夠從一個類更改成它的某個派生類。C# 用in 關鍵字標識逆變量。
協變量,意味着泛型類型參數能夠從一個類更改成它的某個基類。C# 用 out 關鍵字標識協變量。
泛型和其餘成員
在C# 中,屬性、索引器、事件、操做符方法、構造器和終結器自己不能有類型參數。但它們能在泛型類型中定義,並且這些成員中的代碼能使用類型的類型參數。
可驗證型和約束
約束的做用是限制能指定成泛型實參的類型數量。經過限制類型的數量,能夠對哪些類型執行更多的操做。
編譯器/CLR 容許向類型參數應用各類約束。能夠用一個主要約束、一個次要約束以及/或者一個構造器約束來約束類型參數。
主要約束
主要約束能夠是表明非密封類的一個引用類型。不能指定如下特殊引用類型:SystemObject,SystemArray,System.Delegate,System.MulticastDelegate,System.ValueType,System.Enum 或者 System.Void。
指定引用類型約束時,至關於向編譯器承諾:一個指定的類型實參要麼是與約束類型相同的類型,要麼是從約束類型派生的類型。例如如下泛型類:
internal sealed class PrimaryConstraintOfStream<T> where T : Stream { public void M(T stream) { stream.Close();// OK } }
有兩個特殊的主要約束:class 和 struct。其中,class 約束向編譯器承諾類型實參是引用類型。任何類類型、接口類型、委託類型或者數組類型都知足這個約束。
struct 約束向編譯器承諾類型實參是值類型。包括枚舉在內的任何值類型都知足這個約束。可是編譯器和 CLR 將任何System.Nullable<T> 值類型視爲特殊類型,不知足這個 struct 約束。
次要約束
次要約束表明接口類型。這種約束向編譯器承諾類型實參實現了接口。這種約束向編譯器承諾類型實參實現了接口。因爲能指定多個接口約束,因此類型實參必須實現了全部接口約束。
還有一種次要約束稱爲 類型參數約束,有時也稱爲 裸類型約束。它容許一個泛型類型或方法規定:指定的類型實參要麼就是約束的類型,要麼就是約束的類型的派生類。一個類型參數能夠指定零個或者多個類型參數約束。下面這個泛型方法演示瞭如何使用類型參數約束:
private static List<TBase> ConvertIList<T, TBase>(IList<T> list) where T : TBase { List<TBase> baseList = new List<TBase>(list.Count); for (Int32 index = 0; index < list.Count; index++) { baseList.Add(list[index]); } return baseList; }
ConvertList 方法指定了兩個類型參數,其中T 參數由Tbase 類型參數約束。意味着無論爲T 指定什麼類型實參,都必須兼容於爲 TBase 指定的類型實參。
構造器約束
類型參數可指定零個或一個構造器約束,它向編譯器承諾類型實參是實現了公共無參構造器的非抽象類型。如下示例類使用構造器約束來約束它的類型參數:
internal sealed class ConstructorConstraint<T> where T : new() { public static T Factory() { // Allowed because all value types implicitly // have a public, parameterless constructor and because // the constraint requires that any specified reference // type also have a public, parameterless constructor return new T(); } }
在上述例子中 new T() 是合法的,由於已知 T 是擁有公共無參構造器的類型。對全部值類型來講,這一點(擁有公共無參構造器)確定成立。對於做爲類型實參指定的任何引用類型,這一點也成立,由於構造器約束要求它必須成立。
其餘可驗證性問題
一、將泛型類型的變量轉型爲其餘類型是非法的,除非轉型爲與約束兼容的類型。
private static void CastingAGenericTypeVariable1<T>(T obj) { Int32 x = (Int32) obj; // Error String s = (String) obj; // Error } private static void CastingAGenericTypeVariable2<T>(T obj) { Int32 x = (Int32) (Object) obj; // No error String s = (String) (Object) obj; // No error }
二、將泛型類型變量設爲 null 是非法的,除非將泛型類型約束成引用類型。
三、不管泛型類型是否被約束,使用== 或 != 操做符將泛型類型變量與 null 進行比較都是合法的。若是T 被約束成 struct , C# 編譯器會報錯。值類型的變量不能與null 進行比較,由於結果始終同樣。
四、若是泛型類型參數不能確定是引用類型,對同一個泛型類型的兩個變量進行比較是非法的:
private static void ComparingTwoGenericTypeVariables<T>(T o1, T o2) { if (o1 == o2) { } // Error }
五、將操做符用於泛型類型的操做會引起大量問題。C# 知道如何解釋應用於基元類型的操做符(好比+,- ,* 和/)。可是不能將這些操做符用於泛型類型的變量。編譯器在編譯時肯定不了類型。