[CLR via C#]12. 泛型
泛型(generic)是CLR和編程語言提供一種特殊機制,它支持另一種形式的代碼重用,即"算法重用"。
簡單地說,開發人員先定義好一個算法,比如排序、搜索、交換等。但是定義算法的開發人員并不設定該算法要操作什么數據類型;該算法可廣泛地應用于不同類型的對象。然后,另一個開發人員只要指定了算法要操作的具體數據類型,就可以使用這個現成的算法了。
泛型有兩種表現形式:泛型類型和泛型方法。
泛型類型:大多數算法都封裝在一個類型中,CLR允許創建泛型引用類型和泛型值類型,但不允許創建泛型枚舉類型。除此之外,CLR還允許創建泛型接口和泛型委托。
泛型方法:方法偶爾也封裝有用的算法,所以CLR允許引用類型、值類型或接口中定義泛型方法。
兩者都是表示API的基本方法(不管是指一個泛型方法還是一個完整的泛型類型),以致平時期望出現一個普通類型的地方出現一個類型參數。比如,List<T>,在類名之后添加一個<T>,表明它操作的是一個未指定的數據類型。定義泛型類型和方法時,它為類型指定的任何變量(比如 T)都稱為類型參數(type parameter)。T代表一個變量名,在源代碼中能夠使用一個數據類型的任何位置 ,都能使用T。
類型參數是真實類型的占位符。在泛型聲明中,類型參數要放在一堆尖括號內,并以逗號分隔。所以,在Dictionary<TKey, TValue>中,類型參數是TKey和TValue。使用泛型類型或方法時,要使用真實的類型代替。這些真實的類型稱為類型實參(type argument)。
泛型為開發人員提供了以下優勢:
1)源代碼保護 使用一個泛型算法的開發人員不需要訪問算法的源代碼。然而,使用C++模板的泛型技術時,算法的源代碼必須提供給準備使用算法的用戶。
2)類型安全 將一個泛型算法應用于一個具體的類型時,編譯器和CLR能理解開發人員的意圖,并保證只有與制定數據類型兼容的對象才能隨同算法使用。
3)更清晰的代碼 由于編譯器強制類型安全性,所以減少了源代碼中必須進行的轉型次數。
4)更佳的性能 在有泛型之前,要想定義一個常規化的算法,它的所有成員都要定義成操作Object數據類型。這其中就要有裝箱和拆箱之間的性能損失。由于現在能創建一個泛型算法來操作一個具體的值類型,所以值類型的實例能以傳值的方式傳遞,CLR不再需要只需任何裝箱操作。由于不再需要轉型,所以CLR不必檢查嘗試一次轉型操作是否類型安全,同樣提高了代碼的允許速度。
一、 Framework類庫中的泛型
泛型最明顯的應用就是集合類。FCL已經定義了幾個泛型集合類。其中大多數類能在Sysytem.Collections.Generic和System.Collections.ObjectModel命名空間中。要使用線程安全的泛型集合類,可以去System.Collections.Concurrent命名空間尋找。 Microsoft建議開發人員使用泛型集合類,并基于幾個方面的原因,不鼓勵使用非泛型集合類。首先,非泛型無法獲得類型安全性、更清晰的代碼和更佳的性能。其次,泛型具有更好的對象模型。 集合類實現了許多接口,放入集合中的對象也可能實現了接口,集合類可利用這些接口執行像排序這樣的操作。FCL內建了許多泛型接口定義,所以在使用接口時,也能體會到泛型帶來的好處。常用的接口包含在Sysytem.Collections.Generic命名空間中。 新的泛型接口并不是設計用來完全取代非泛型接口。 System.Array類(即所有數組的基類)提供了大量靜態泛型方法,比如,AsReadonly、FindAll、Find、FindIndex等。二、Wintellect的Power Collections庫
Power Collections庫由Wintellect制作,這個庫有一系列集合類構成,任何人都可以免費下載和使用?! ?/p>
| 集合類名稱 | 說明 |
| BigList<T>? | 有序T對象集合。操作100個以上的數據項是,效率非常高 |
| Bag<T> | 無序T對象的集合,集合進行了哈希處理,并允許重復項 |
| OrderedBag<T> | 有序T對象的集合,允許重復值 |
| Set<T> | 無序T數據項集合,不允許重復項。添加重復項后,會只保留一個 |
| OrderedSet<T> | 有序T數據項的集合,不允許重復項 |
| Deque<T> | 雙端隊列(double-ending queue)。類似于一個列表,但在起始處添加/刪除數據項時,比列表更高效 |
| OrderedDictionary<TKey,TValue> | 字典,其中的鍵進行了排序,每個鍵都有一個對應的值 |
| MultiDictionary<TKey,TValue> | 字典,其中每個鍵都可以有多個值,對鍵進行了哈希處理,允許重復,而且數據項是無序的 |
| OrderedMultiDictionary<TKey,TValue> | 字典,其中的鍵進行了排序,每個鍵都可以有多個值(同樣進行了排序)。允許重復的鍵 ? |
?
?
?
三、泛型的基礎結構
為了是泛型能夠工作,Microsoft必須完成以下工作:
1)創建新的IL指令,使之能夠識別類型實參 2)修改現有元數據表的格式,以便表示具有泛型參數的類型名稱和方法 3)修改各種編程語言(C#等),以支持新的語法,允許開發人員定義個引入泛型類型和方法 4)修改編譯器,使之能生成新的IL指令和修改元數據格式 5)修改JIT編譯器,使之能夠處理新的、支持類型實參的IL指令,以便生成正確的本地代碼 6)創建新的反射成員,使開發人員能查詢類型和成員,以判斷它們是否具有泛型參數。另外,還必須定義新的反射成員,使開發人員能在運行時創建泛型類型和方法定義。 7)修改調試器以以顯示和操作泛型類型、成員、字段以及局部變量。 8)修改VisualStudio 的"智能感知"(IntelliSense)特性。 1.開放類型和封閉類型 前面我們討論過CLR如何為應用程序的每個類型創建一個內部數據結構,這種數據結構稱為類型對象。 具有泛型類型參數的類型仍然是類型,CLR同樣會為它創建一個內部類型對象。無論是引用類型(類)、值類型(結構)、接口類型,還是委托類型,這一點都是成立的。 如果沒有為任何類型參數提供類型實參,聲明的就是一個未綁定泛型類型。 如果指定了類型實參,該類型就稱為已構造類型。 我們知道,類型可以看做是對象的藍圖。同樣的,未綁定泛型類型是已構造類型的藍圖。它是一種額外的抽象層。 已構造類型可以是開放類型或封閉類型。 "開放類型"(open type)是指還包含一個類型參數,CLR禁止構造開放類型的任何實例。這一點類似于CLR禁止構造接口類型的實例。 代碼引用一個泛型類型時,可指定一組泛型類型實參。假如為所有類型實參傳遞的都是實際數據類型,類型就稱為"封閉類型"(closed type)。也就是說,具有泛型"類型實參"的類型稱為"封閉類型"。CLR允許構造封閉類型的實例。 當代碼引用一個泛型類型時,可能會留下一些泛型類型實參未指定。這會在CLR中創建一個新的開放類型的對象,而且不能創建該類型的實例。比如: internal static class Program{private static void Main(string[] args){Object o = null;// Dictionary<,> 是一個開放類型,有兩個類型參數Type t = typeof(Dictionary<,>);// 嘗試創建該類型的一個實例 (失敗)o = CreateInstance(t);Console.WriteLine();// DictionaryStringKey<> 是一個開放類型,有一個類型參數t = typeof(DictionaryStringKey<>);// 嘗試創建該類型的一個實例 (失敗)o = CreateInstance(t);Console.WriteLine();// DictionaryStringKey<Guid> 是一個封閉類型t = typeof(DictionaryStringKey<Guid>);// 嘗試創建該類型的一個實例 (成功)o = CreateInstance(t);// Prove it actually workedConsole.WriteLine("Object type=" + o.GetType());Console.ReadKey();}private static Object CreateInstance(Type t){Object o = null;try{o = Activator.CreateInstance(t);Console.Write("已創建 {0} 的實例", t.ToString());}catch (ArgumentException e){Console.WriteLine(e.Message);}return o;}// A partially specified open typeinternal sealed class DictionaryStringKey<TValue> :Dictionary<String, TValue>{}}最后顯示地結果為:
可以看出,Activator的CreateInstance方法會在構造開發類型的實例時拋出一個ArgumentException異常。注意,在異常的字符串消息中,指明類型中仍然含有一些泛型參數。 從輸出結果可以看出,類型名是以一個"`"字符和一個數字結尾的。這個數字代表類型的元數,也就是類型要求的類型參數的個數。例如,Dictionary類的元數為2,它要求為TKey和TValue這兩個類型參數指定具體類型。 還要注意的是,CLR會在類型對象內部分配類型的靜態字段。因此,每個封閉類型都有自己的靜態字段。換言之,假如List<T>定義了任何靜態字段,這些字段不會在一個List<DataTime>和List<String>之間共享;每個封閉類型對象都有它自己的靜態字段。另外,假如一個泛型類型定義了一個靜態構造器,那么針對每個封閉類型,這個構造器都會執行一次。在泛型類型上定義一個靜態構造器的目的是保證傳遞的類型參數滿足特定的條件。例如,如果希望一個泛型類型值用于處理枚舉類型,可以如下定義: internal sealed calss GenericTypeThatReqiresAnEnum<T> {static GenericTypeThatReqiresAnEnum() {if ( !typeof (T).IsEnum) {throw new ArgumentException("T must be an enumerated type")}} }
CLR提供了一個名為"約束"(constraint)的功能,可利用它更好地定義一個泛型類型來指出哪個類型實參是有效的。
2.泛型類型和繼承 泛型類型仍然是類型,所以它能從其他任何類型派生。使用一個泛型類型并指定類型實參時,實際上是在CLR中定義一個新的類型對象,新的類型對象是從派生該泛型類型的那個類型派生的。也就是說,由于List<T>是從Object派生的,那么List<String>和List<Guid>也是從Object派生的。 3. 泛型類型同一性 有的時候,泛型語法會將開發人員搞糊涂,所以有的開發人員定義了一個新的非泛型類類型,它從一個泛型類型派生,并指定了所有的類型實參。例如,為了簡化一下代碼: List<DateTime> dt = new List<DateTime>();一些開發人員可能首先定義下面這樣的一個類:
internal sealed class DateTimeList : List<DataTime> {//這里無需放任何代碼! }然后就可以進一步簡化創建:
DateTimeList dt = new DateTimeList ();這樣做表面上是方便了,但是決定不要單純處于增強源代碼的易讀性類這樣定義一個新類。這樣會喪失類型同一性(identity)和相等性(equivalence)。如下:
Boolean sameType = (typeof(List<DateTime>) == (typeof(DateTimeList));上述代碼運行時,sameType會初始化為false,因為比較的是兩個不同類型的對象。也就是說,假如一個方法的原型接受一個DateTimeList,那么不能將一個List<DateTime>傳給它。然而,如果方法的原型接受一個List<DateTime>,那么可以將一個DateTimeList傳給它,因為DateTimeList是從List<DateTime>派生的。
C#提供一種方式,允許使用簡化的語法來引用一個泛型封閉類型,同時不會影響類的相等性——使用using指令。比如: using DateTimeList = System.Collections.Generic.List<System.DateTime>;現在只想下面這行代碼時,sameType會初始化為true:
Boolean sameType = (type(List<DateTime>) == (ypeof(DateTimeList));還有,可以使用C#的隱式類型局部變量功能,讓編譯器根據表達式的類型來推斷一個方法的局部變量的類型。
4.代碼爆炸 使用泛型類型參數的一個方法在進行JIT編譯時,CLR獲取方法的IL,用指定的類型實參進行替換,然后創建恰當的本地代碼。然而,這樣做有一個缺點:CLR要為每種不同的方法/類型組合生成本地代碼。我們將這個現象稱為"代碼爆炸"。它可能造成引用程序集的顯著增大,從而影響性能。 CLR內建了一些優化措施,能緩解代碼爆炸。首先,假如為一個特定的類型實參調用了一個方法,以后再次使用相同的類型實參來調用這個方法,CLR只會為這個方法/類型組合編譯一次。所以,如果一個程序集使用List<DateTime>,一個完全不同的程序集也使用List<DateTime>,CLR只會為List<DateTime>編譯一次方法。 CLR還提供了一個優化措施,它認為所有引用類型實參都是完全相同的,所以代碼能夠共享。之所以能這樣,是因為所有引用類型的實參或變量時間只是執行堆上的對象的指針,而對象指針全部是以相同的方式操作的。 但是,假如某個類型實參是值類型,CLR就必須專門為那個值類型生成本地代碼。因為值類型的大小不定。即使類型、大小相同,CLR仍然無法共享代碼,可能需要用不同的本地CPU指令操作這些值。 四、泛型接口 泛型的主要作用就是定義泛型的引用類型和值類型。然而,對泛型接口的支持對CLR來說也很重要。沒有泛型接口,每次試圖使用一個非泛型接口(如IComparable)來操作一個值類型,都會發生裝箱,而且會失去編譯時的類型安全性。這將嚴重制約泛型類型的應用。因此,CLR提供了對泛型接口的支持。一個引用類型或值類型可以通過指定類型實參的方式來實現泛型接口。也可以保持類型實參的未指定狀態來實現一個泛型接口。 以下是泛型接口定義是FCL的一部分: public interface IEnumerator<T> : IDisposable, IEnumerator{T Current { get; } }下面的示例類型實現上述泛型接口,而且指定了類型實參。
internal sealed class Triangle : IEnumerator<Point> {private Point[] m_Vertice;public Point Current { get { ... } } }下面實現了相同的泛型接口,但保持類型實參的未指定狀態:
internal sealed class ArrayEnumerator<T> : IEnumerator<T> {private T[] m_Vertice;public TCurrent { get { ... } } }?
五、泛型委托
CLR支持泛型委托,目的是保證任何類型的對象都能以一種類型安全的方式傳給一個回調方法。
此外,泛型委托允許一個值類型的實例在傳給一個回調方法時不執行任何裝箱操作。
委托實際只提供了4個方法的一個類定義。這4個方法包括一個構造器、一個Invoke方法、一個BeginInvoke和一個EndInvoke方法。如果定義的一個委托類型指定了類型參數,編譯器會定義委托類的方法,用指定的類型參數替代方法中的參數類型和返回值類型。 例如,假定向下面這樣定義一個泛型委托: public delegate TReturn CallMe<TReturn, TKey, TValue>(TKey key, TValue value);編譯器會將它轉化成一個類,該類在邏輯上可以這樣表示:
public sealed class CallMe<TReturn, TKey, TValue> : MulticastDelegate {public CallMe(Object object, IntPtr method);public virtual TReturn Invoke(TKey key, TValue value);public virtual IAsycResult BeginInvoke(TKey key, TValue value, AsyncCallback callback, Object object);public virtual TReturn EndInvoke(IAsycResult result); }?反編譯后
? 建議盡量使用在FVL中預定義的泛型Action和Func委托。
?六、?委托和接口的逆變和協變泛型類型實參 委托的每個泛型類型參數都可標識為協變量或者逆變量。利用這個功能,可將泛型委托類型的一個變量轉型為同一個委托類型的另一個變量,后者的泛型參數類型不同。泛型類型參數可以是一下任何一種形式: 1)不變量(invariant)????意味著泛型類型參數不能更改。 2)逆變量(Contravarriant)????意味著泛型類型參數可以從一個基類更改為該類的派生類。在C#中,用 in 關鍵字標識逆變量形式的泛型類型參數。逆變量泛型參數只出現在輸入位置,比如作為方法的參數。 3)協變量(Convarianr) ? ?意味著泛型類參數可以從一個派生類更改為它的基類。在C#中用 out 關鍵字標記協變量形式的泛型類型參數。協變量泛型參數只能出現在輸出位置,比如作為方法的返回類型。 例如,現在存在以下委托類型定義(它在FCL中是存在的)? public delegate TResult Func<in T, Out TResult>(T arg);其中,泛型類型參數T用in關鍵字標記,這使它成為一個逆變量;泛型類型參數TResulr則用out關鍵字標記,這是它成為一個協變量。
所以,如果像下面這樣聲明一個變量: Func<Object,ArgumenException> fn1 = null;就可以將它轉型為另一個泛型類型參數不同的Func類型:
Func<String,Exception> fn2 = fn1; //不需要顯示轉型 Exception e = fn("");使用要獲取泛型參數和返回值的委托時,建議盡量為逆變性和協變性指定in和out關鍵字。這樣做不會有不良反應,并使你的委托能在更多的情形中使用。
和委托相似,具有泛型類型參數的接口也可將它的類型參數標記為逆變量和協變量。比如: public interface IEnumerator<out T> : IEnumerator {Boolean MoveNext();T Current{ get; } }由于T是逆變量,所以以下代碼可以順利編譯:
//這個方法接受任意引用類型的一個IEnumerable Int32 Count(IEnumerable<Object> collection) { ... } //以下調用向Count傳遞一個IEnumerable<String> Int32 c = Count(new[] { "Grant" });?
七、泛型方法
定義泛型類、結構或接口時,這些類型中定義的任何方法都可引用由類型指定的一個類型參數。類型參數可以作為方法的參數,作為方法的返回值,或者作為方法內部定義的一個局部變量來使用。
CLR還允許一個方法指定它獨有的類型參數。這些類型參數可用于參數、返回值或者局部變量。 在下面的例子中,一個類型定義了一個類型參數,一個方法則定義了它自己的專用類型參數: internal sealed class FenericType<T> {privete T m_value;public GenericType(T value) { m_value = value; }public TOutput Converter<TOutput>() {TOutput resulr= (TOurput) Convert.ChangeType(m_value,typeof(TOutput));return result;} }1.泛型方法和類型推斷
為了改進代碼的創建,同事增強可讀性和維護性,C#編譯器支持在調用一個泛型方法時進行類型推斷(type inference)。這意味著編譯器會在調用一個泛型方法時自動判斷出要使用的類型。 private static void CallingSwapUsingInference() {Int32 n1 = 1, n2 = 2;Swap(ref n1, ref n2); //調用Swap<Int32> String s1 = "A";Object s2 = "B";Swap(ref s1, ref s2); //錯誤,不能推斷類型 }執行類型推斷時,C#使用變量的數據類型,而不是由變量引用的對象的實際類型。
八、泛型和其他成員
在C#中,屬性、索引器、事件、操作符方法、構造器和終結器(finalizer)本身不能有類型參數。但是,它們能在一個泛型類型中定義,而且這些成員中的代碼能使用類型的類型參數。
?
九、可驗證性和約束
C#編譯器和CLR支持一個稱為"約束"(constraint)的機制,可利用它使泛型變得真正有用。約束的作用是限制能指定成泛型實參的參數數量。通過限制類型的數量,我們可以對那些類型執行更多的操作。 public static T Min<T>(T o1, T o2) where T : IComparable<T> {if (o1.CompareTo(o2))<0 return o1;return o2; }C#的where關鍵字告訴編譯器,為T指定的任何類型都必須實現同類型(T)的泛型IComparable接口。有了這個約束,就可以在方法中調用CompareTo,因為已知IComparable<T>接口定義了CompareTo。
約束可應用于一個泛型類型的類型參數,也可應用于一個泛型方法的類型參數(就像Min所展示的)。CLR不允許基于類型參數名稱或約束來進行重載;只能基于元數(類型參數的個數)對類型或方法進行重載。 internal sealed class OverloadingByArity {// 可以定義一下類型internal sealed class AType { }internal sealed class AType<T> { }internal sealed class AType<T1, T2> { }// 錯誤: 與沒有約束的 AType<T> 起沖突internal sealed class AType<T> where T : IComparable<T> { }// 錯誤: 與 AType<T1, T2> 起沖突internal sealed class AType<T3, T4> { }internal sealed class AnotherType {// 可以定義一下方法,參數個數不同:private static void M() { }private static void M<T>() { }private static void M<T1, T2>() { }// 錯誤: 與沒有約束的 M<T> 起沖突private static void M<T>() where T : IComparable<T> { }// 錯誤: 與 M<T1, T2> 起沖突private static void M<T3, T4>() { }}} 重寫一個虛泛型方法時,重寫的方法必須指定相同數量的類型參數,而且這些類型參數會繼承在基類方法上指定的約束。事實上,根本不允許為重寫方法的類型參數指定任何約束。但是,類型參數的名稱是可以改變的。類似的,實現一個接口方法時,方法必須指定與接口方法等量的類型參數,這些類型參數將繼承由接口的方法在它們前面指定的約束。下例使用虛方法演示了這一規則: internal static class OverridingVirtualGenericMethod {internal class Base {public virtual void M<T1, T2>()where T1 : structwhere T2 : class {}}internal sealed class Derived : Base {public override void M<T3, T4>()/*where T3 : structwhere T4 : class */{}}}1 主要約束
類型參數可以指定零個或一個主要約束。主要約束可以是一個引用類型,它標志了一個沒有密封的類。不能指定以下特殊引用類型:System.Object,System.Array,System.Delagate,System.MulticastDelegate,System.ValueType,System.Enum和System.Void。 指向一個引用類型約束時,相當于向編譯器承諾:一個指定的類型實參要么是與約束類型相同的類型,要么是從約束類型派生的一個類型。如下泛型類: internal static class PrimaryConstraintOfStream<T> where T : Stream {public static void M(T stream) {stream.Close(); // OK }}有兩個特殊的主要約束:class和struct。其中,class約束是指類型實參是一個引用類型。任何類類型、接口類型、委托類型或者數組類型都滿足這個約束。例如:
internal static class PrimaryConstraintOfClass<T> where T : class {public static void M() {T temp = null; // 允許,T為引用類型 }}struct約束向編譯器承諾一個指定的類型實參是值類型。包括枚舉在內的任何值類型都滿足這個約束。然而,編譯器和CLR將任何System.Nullable<T>值類型都視為特殊類型。
internal static class PrimaryConstraintOfStruct<T> where T : struct {public static T Factory() {// 允許,因為值類型都有一個隱式無參構造器return new T();}}?
2 次要約束 一個類型參數可以指定零個或者多個次要約束,次要約束代表的是一個接口類型。指定一個接口類型約束時,是向編譯器承諾一個指定的類型實參是是實現了接口的一個類型。由于能指定多個接口約束,所以為類型實參指定的類型必須實現了所有接口約束。 還有一種次要約束稱為類型參數約束,有時也稱裸類型約束。這種約束用的比接口約束少得多。它允許一個泛型類型或方法規定:在指定的類型實參之間,必須存在一個關系。一個類型參數可以指定零個或者多個類型參數約束。下面這個泛型方法演示了如何使用類型參數約束: internal static class SecondaryConstraints{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;}private static void CallingConvertIList(){//構造并初始化一個List<String>(它實現了IList<String>)IList<String> ls = new List<String>();ls.Add("A String");// 將IList<String>轉換成IList<Object>IList<Object> lo = ConvertIList<String, Object>(ls);// 將IList<String>轉換成IList<IComparable>IList<IComparable> lc = ConvertIList<String, IComparable>(ls);// 將IList<String>轉換成IList<IComparable<String>>IList<IComparable<String>> lcs =ConvertIList<String, IComparable<String>>(ls);// 將IList<String>轉換成IList<Exception>//IList<Exception> le = ConvertIList<String, Exception>(ls); // 錯誤 }}?
3 構造器約束 一個類型參數可以指定零個或者一個構造器約束。指定構造器約束相當于向編譯器承諾:一個指定的類型實參是實現了公共無參構造器的一個非抽象類型。 internal sealed class ConstructorConstraints{internal sealed class ConstructorConstraint<T> where T : new(){public static T Factory(){// 允許,因為值類型都有隱式無參構造器// 而約束要求任何引用類型也要有一個無參構造器return new T();}}}目前,Microsoft只支持無參構造器約束。
4 其他可驗證問題 1)泛型類型變量的轉型 將一個泛型類型的變量轉型為另一個類型是非法的,除非將其轉型為與一個約束兼容的類型: private void CastingAGenericTypeVariable1<T>(T obj){Int32 x = (Int32)obj; // 錯誤String s = (String)obj; // 錯誤}上述兩行錯誤是因為T可以是任何任何類型,無法保證成功。
private void CastingAGenericTypeVariable2<T>(T obj) {Int32 x = (Int32)(Object)obj; // 不報錯String s = (String)(Object)obj; // 不報錯 }現在雖然能編譯通過,但運行時也無法保證是正確的。
2)將一個泛型類型變量設為默認值 將泛型類型變量設為null是非法的,除非將泛型 類型約束成一個引用類型: internal sealed class SettingAGenericTypeVariableToADefaultValue{private void SettingAGenericTypeVariableToNull<T>(){//T temp = null; // 錯誤, 值類型不能設置為null,可考慮使用default('T') }private void SettingAGenericTypeVariableToDefaultValue<T>(){T temp = default(T); // 正確 }}?
3)將一個泛型類型變量與null進行比較 無論泛型類型是否非約束,使用==或!=操作符將一個泛型類型變量與null進行比較都是合法的。 private void ComparingAGenericTypeVariableWithNull<T>(T obj) {if (obj == null) { /* 對值類型來說,永遠不會執行 */ } }如果T被約束成一個struct,C#編譯器會報錯。
4)兩個泛型類型變量相互比較 如果泛型類型參數不是一個引用類型,對同一個泛型類型的兩個變量進行比較是非法的: private void ComparingTwoGenericTypeVariables<T>(T o1, T o2) {//if (o1 == o2) { } // 錯誤 }5)泛型類型變量作為操作書使用
將操作符應用于泛型類型的操作數,會出現大量問題。不能將操作符應用于泛型,因為編譯器在編譯時無法確定類型。 internal static class UsingGenericTypeVariablesAsOperands {private T Sum<T>(T num) where T : struct {T sum = default(T);for (T n = default(T); n < num; n++)sum += n;return sum;} }上面代碼會出很多錯誤,比如:運算符"<"無法應用于"T"和"T"類型的操作數等。
這是CLR的泛型支持體系的一個嚴重限制。總結
以上是生活随笔為你收集整理的[CLR via C#]12. 泛型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Sencha touch Panel之间
- 下一篇: MATLAB读取HDF格式的SST数据