終於講到泛型了。當初看到這個書名,最想看的就是作者對泛型,委託,反射這些概念的理解。很多人對泛型的理解停留在泛型集合上,剛開始我也是,隨著專案越做越多,對待泛型的認識也越來越深刻。
泛型的概念:泛型是一種特殊的型別,它把指定型別的工作推遲到客戶端程式碼宣告並例項化類或方法的時候進行。
泛型的優勢:原始碼保護、型別安全、更加清晰的程式碼、更佳的效能。
原理:(關鍵字:開放型別,封閉型別)所有帶泛型引數的型別都是一個開放式型別,它不能被例項化(類似介面),在具體使用時生成封閉型別(實際資料型別)。
泛型約束(至多一個主要約束,次要約束無限制):
泛型約束的使用:
/// <summary> /// 未加約束的T可以是任何型別,許多型別沒有提供CompareTo方法,沒有約束將導致程式碼不能編譯,報"'T'不包含'CompareTo'的定義"錯誤 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="o1"></param> /// <param name="o2"></param> /// <returns></returns> private static T Min<T>(T o1,T o2) where T : IComparable<T> { if (o1.CompareTo(o2) < 0) return o1; return o2; }
構造器約束:
//因為所有值型別都隱式有一個公共無參構造器。約束要求指定的任何引用型別也要有一個公共無參構造器 internal sealed class ConstructorConstraint<T> where T:new(){ public static T Factory(){ return new T(); } }
由於泛型型別引數不能指定以下特殊引用型別:System.Object,System.Array,System.Delegate,System.MulticastDelegate,System.ValueType,System.Enum,System.Void,一些實參限制的實現可能要“特殊處理”,如以下使用靜態構造器來保證型別是一個列舉型別。
internal sealed class GenericTypeThatRequiresAnEnum<T>{ static GenericTypeThatRequiresAnEnum(){ if(!typeof(T).IsEnum){ throw new ArgumentException("T must be an enumerated type"); } } }
泛型介面的優勢:沒有泛型介面,每次檢視使用一個非泛型介面來操作一個值型別,都會發生裝箱,而且會失去編譯時的型別安全性。
委託和介面的逆變和協變泛型型別實參:
不變數:意味著泛型型別引數不能更改。(常用)
逆變數:意味著泛型型別引數可以從一個基類更改為該類的派生類。在C#中,用in關鍵字標記逆變數形式的泛型型別引數。逆變數泛型型別引數只出現在輸入位置,比如作為方法的引數。
協變數:意味著泛型型別引數可以從一個派生類更改為它的基類。在C#中,是用out關鍵字標記協變數形式的泛型型別引數。協變數泛型型別引數只能出現在輸出位置,比如作為方法的返回型別。
public delegate TResult Func<in T,out TResult>(T arg);
其它重要認知:
型別實參的指定和繼承層次結構沒有任何關係--理解這一點,有助於你判斷轉型的進行。
C#允許使用簡化的語法來引用一個泛型封閉型別
using DateTimeList = System.Collections.Generic.List<System.DateTime>;
現在執行下面這行程式碼時,sameType會被初始化為true:
Boolean sameType = (typeof(List<DateTime>) == typeof(DateTimeList));
CLR支援泛型委託,目的是保證任何型別的物件都能以一種型別安全的方式傳給一個回撥方法。此外,泛型委託允許任何一個值型別例項在傳給一個回撥方法時不執行任何裝箱處理。
一些驗證問題:
1. 泛型型別變數的轉型
將一個泛型型別的變數轉型為另一個型別是非法的,除非將其轉型為另一個約束相容的型別
private static void CastingType<T>(T obj){ Int32 x = (Int32)obj;//錯誤 String s = (String)obj;//錯誤 string s2 = obj as String;//無錯誤 }
2. 設定預設值
private static void SettingDefaultValue<T>(){ T temp = default(T); }
default關鍵字告訴C#編譯器和CLR的JIT編譯器,如果T是一個引用型別,就將temp設為null,如果T是一個值型別,就將temp的所有位設為0。