泛型是CLR和編程語言提供的一種特殊機制,它用於滿足“算法重用” 。
可以想象一下一個只有操作的參數的數據類型不同的策略模式,完全可以用泛型來化為一個函數。
以下是它的優勢:
這就是為什麼List<T>淘汰了ArrayList的原因,特別是在進行值類型操作時,因為裝箱拆箱過多而差距很大。
約定:泛型參數要麼為T要麼以大寫T開頭,例如List<T>。
FCL中的泛型
System.Collections.Generic和System.Collections.ObjectModel命名空間中提供了多個泛型集合類和接口。
System.Collections.Concurrent命名空間則提供線程安全的泛型集合類。
System.Array類則提供了大量的靜態泛型方法。
泛型的基礎結構
.net 2.0才有泛型。
委托和接口的逆變和協變泛型類型實參
泛型委托和接口的每個泛型類型參數都可標記為協變量和逆變量,利用此功能可實現相同類型但實參類型不同的委托和接口的相互轉換。(很繞,不明白可以看下面)
舉個例子
public class 基類 { } public class 派生類 : 基類 { } public class Test{ public delegate TResult MyFunc<in T1, out TResult, T2>(T1 a, T2 b);//第一個為逆變量,第二個為協變量,第三個為不變量 void show() { MyFunc<基類, 基類, 基類> fn1 = null; //以下注釋為我自己的理解方式,只是為了方便理解而已 MyFunc<派生類, 基類, 基類> fn2 = fn1;//MyFunc<派生類, 派生類, 基類> fn2 = fn1;轉換錯誤 MyFunc<基類, Object, 基類> fn3 = fn1;//MyFunc<Object, Object, 基類> fn3 = fn1;轉換錯誤 MyFunc<派生類, Object, 基類> fn4 = fn1; } }
依然很繞,實際上不懂也沒關系,轉換不了編譯器自然會提示。了解有這個東西就行了,也建議用int和out指定泛型委托的類型變量。更多的時候我們會用自帶的泛型委托Action和Func,這兩個泛型委托的參數都用到in和out。
關於泛型方法的類型推斷
void Go() { String s1 = "213"; Object s2 = "123"; Show(s1, s2);//不指定Show<T>的T的玩法就叫類型推斷,類型推斷通過傳入的變量s1和變量s2的變量類型來推斷,而不是實際類型。因為這裡兩個變量類型不同,所以函數編譯不通過。 } void Show<T>(T a,T b) { }
約束
泛型的約束是一個很有意思的事情。
void Show<T>(T a,T b) where T :IList { }
比如上面這個函數,約束傳入的類型T必須實現了IList接口。
通過約束可以限制傳入的類型,然而正式因為提供了這層約束,保證了傳入的類型都實現了IList接口,我們就可以使用IList的各種方法了。
約束分類:
可驗證性
以下幾種情況因為代碼不可驗證是否合法,所以將報錯:
void Show<T>(T obj){ string a=(string)obj; //出錯 }
void Show<T>(T obj) { string a = obj as string;//對於string而言,其實這裡用ToString方法可能更恰當一點 }
值類型可以先強制轉換為object,再轉為具體的值類型。然而我認為這樣的代碼還是需要開箱裝箱的,也許可以考慮修改下算法。