泛型:支持值類型和引用類型,不支持枚舉。
沒有泛型屬性。
泛型的好處:
源代碼保護。使用泛型算法不需要訪問算法的源碼——相對於C++模板
類型安全——相對於ArrayList
更加清晰的源碼——不需要拆箱,顯示轉換
更佳的性能——不用裝箱。測試:循環1000萬次,泛型List<T>與ArrayList分別用時0.1s和 2s
16.1 FCL中的泛型
List<T> 取代ArrayList
Directory<TKey, TValue>取代HashTable
Stack<T>,Queue<T>分別取代Stack,Queue
IList,IDirectory,ICollection,IEnumerator,IEnumerable,IComparer,IComparable分別由相 應的泛型接口(加上<T>)
16.3 泛型基礎結構
這一節的前言很有意思:如何在已有的CLR中添加泛型:
創建新的IL泛型指令
修改元數據格式,以支持泛型參數/類型/方法
修改各種語言C#/VB.NET
修改編譯器csc,使之生成新的IL泛型指令/元數據
修改JITer,使之可以處理新的IL泛型指令
創建新的反射成員:泛型參數/類型/方法
修改調試器
修改vs2005智能感知
CLR為應用程序使用的每個類型創建一個內部數據結構,稱為“類型對象”。
具有泛型類型參數的一個類型class<T>,仍然是一個類型,也具有一個類型對象,稱為“開放 式類型”,CLR禁止構造開放式類型的實例;
當T是一個實際類型時class<Guid>,稱為“封閉式類型”,CLR中可以創建封閉式類型的實例。
語法Activator.Creator(type):根據type獲取其一個實例。有
typeof(Dictionary< , >) //運行期會報錯,使用了“開放式類型”
又
t = typeof(Dictionary<String , String>)
Console.WriteLine(t.ToString()); //輸出 System.Collections.Generic.Dictionary`2 [System.String,System.String],這就是泛型Dictionary<String , String>的類型,在IL中也生 成諸如Dictionary`2的方法,這裡,2表示類型參數的數量。
每個封閉式類型都有自己的靜態字段,也就是說,List<String>和List<Int32>具有各自 的靜態字段和cctor。
在泛型上定義cctor的目的:為類型參數加限制條件。例如,希望泛型只用於處理enum——解決了無法 用約束控制Enum:
internal class GenericOnlyForEnum<T> { static GenericOnlyForEnum() { if (!typeof(T).IsEnum) { throw new Exception(); } } }
泛型類型與繼承
由於List<T>從System.Object派生,所以List<String>也是從System.Object派生。
書中演示了一個例子——定義並使用一個鏈表節點類,很值得琢磨,原理就是利用上面這句話。
方法1:
class Node<T> { public T m_data; public Node<T> m_next; public Node(T data) : this(data, null) { } public Node(T data, Node<T> next) { m_data = data; m_next = next; } } public static class Program { static void Main() { Node<Char> head = new Node<char>('C'); head = new Node<char>('B', head); head = new Node<char>('A', head); } }
評論:這個方法不靈活,只能構造同一種泛型Node<char>。
其實呢,是因為Node<T>這個類定義的太大了,可以一拆為二:Node類和TypeNode<T>派 生類,其中Node類只記錄next節點。而在其子類TypeNode<T>中,設置具體數據,如下方法2:
class Node { protected Node m_next; public Node(Node next) { m_next = next; } } class TypeNode<T> : Node { public T m_data; public TypeNode(T data) : this(data, null) { } public TypeNode(T data, Node next) : base(next) { m_data = data; } }
這時候,就可以任意設置數據了,因為這次head聲明為Node基類型,所以可以改變其子類:
static void Main() { Node head = new TypeNode<Char>('A'); head = new TypeNode<DateTime>(DateTime.Now, head); }
泛型類型同一性:
using XXX = System.Collections.Generic.List<System.DateTime>;
接下來就可以使用XXX來代替List<System.DateTime>
——這只是對封閉性類型而言的,一種簡寫
代碼爆炸:
對於泛型,CLR為每種不同的方法/類型組合生成本地代碼,乘法原理——Assembly越來越大
由此,CLR有專門優化措施:
對於引用類型的類型實參,只編譯一次,以後可以共享代碼——因為都是指針,可以看作相同類型; 對於值類型,不能共享,還是要“爆炸”的。
16.4 泛型接口
以FCL中的IEnumerator<T>為例:
public interface IEnumerator<T> : IDisposable, IEnumerable { T Current { get;} }
有兩種實現方式:
//1.指定T class A : IEnumerable<String> { private String current; public String Current { get { return current; } } } //2.不指定T class B : IEnumerable<T> { private T current; public T Current { get { return current; } } }
16.5 泛型委托
泛型委托允許值類型實例傳遞給回調方法時不裝箱。
16.6 泛型方法
如果泛型類中有泛型方法,則各自聲明的類型參數不能重名。
泛型類的ctor等於同名非泛型類的ctor
使用泛型方法和ref進行Swap操作
類型推導:自動判斷泛型方法要使用的類型,如下:
static void Swap<T>(ref T o1, ref T o2) { }
可以直接放入兩個Int32:
Int32 n1 = 1, n2 = 2; Swap(ref n1, ref n2);
但是,以下使用會在編譯期報錯,即使是實際引用相同的類型,但是推導是根據聲明類型來判斷的:
String s1 = "s1"; Object s2 = "s2"; Swap(ref s1, ref s2);
泛型方法重載:
static void Display(String s) { Console.WriteLine(s); } static void Display<T>(T o) { Display(o.ToString()); }
這時,調用方法就有學問了:
Display("s1");
編譯器總是優先選擇更顯示的匹配,於是以上語句直接調用非泛型方法;否則會陷入死循環。
16.7 泛型和其他成員
C#中,屬性/索引/事件/操作符方法/ctor/finalzer不能有泛型方式。
允許有的:class/struct/interface/funcction/delegate
16.8 可驗證性和限制
在泛型中,類型參數T,只能使用System.Object的方法,比如說ToString();不能使用其他任何方法 ,因為不知道T類型是否支持該方法。
使用where約束,從而可以使用T的更多方法:
where T:Icomparable<T> 告訴編譯器,T表示的類型,必須實現相應的 Icomparable<T>接口,從而T類型實例可以使用該接口的CompareTo方法。
where可以用於class和function
泛型方法重載原則:只能基於類型參數的個數進行重載,個數為0表示非泛型方法。
對於class,可以有:
class AType { } class AType<T> { } class AType<T1, T2> { } //class AType<T1, T2> where T : IComparable<T> { }
被注釋的類不可以再聲明——class也是根據類型參數的個數,但是不考慮where約束的有無
對於泛型虛方法,重寫的版本必須指定相同數量的參數類型,會繼承基類方法的約束,但是不可以再 指定任何新的約束。
3種約束:
1.主要約束:class和struct,分別約束類型參數為引用類型和值類型
class ForStruct<T> where T : struct { public T Test() { return new T(); //只有值類型可以new T(); } } class ForClass<T> where T : class { public void Test() { T temp = null; //只有引用類型可以使用null } }
對於T:class約束,T可以是類/接口/委托/數組類型
2.次要約束:接口約束和裸類型約束
接口約束見14章筆記,這裡主要講裸類型約束。
裸類型約束,在類型參數間存在一個關系where T1:T2,這裡可以寫為where String:String,即兼 容於自身
3.構造器約束:類型參數一定實現了public的無參構造器:where T:new()
不能同時指定構造器約束和struct約束——這是多余的,因為所有值類型都隱式實現public無參構造 器
上面提到:只有值類型可以new T(); 但是,如果有了new()約束,引用類型也可以使用new T()了,因 為肯定有構造器,所以new的動作總是可以執行。示例如下:
public class ConstructConstraint<T> where T : new() { public static T Factory() { return new T(); } } public static class Program { static void Main() { ConstructConstraint<A>.Factory(); } }
其他可驗證問題:
1.不能顯示將泛型類型變量轉型,要先轉為Object,再顯示轉換;對於值類型,要使用as操作符來控 制運行期錯誤
static void CastingGeneric<T>(T obj) { //Int32 x = (Int32)obj; 不能這麼寫 //值類型,應該這麼寫 Int32 x = (Int32)(Object)obj; //引用類型,最好這麼寫 String s = obj as String; }
2.使用default關鍵字來為泛型變量設置默認值:
T temp = default(T); //null或者0
3.將泛型變量與null進行比較
對於引用類型,無論是否被約束,都可以使用==和!=比較null
對於值類型,如果未被約束,則永遠為null,因為if(obj==null)永遠為真,這裡並不會報錯;
如果被約束為struct,則不能比較null,因為if(obj==null)結果永遠為真,但是這裡會報錯。
4.兩個泛型類型變量的比較,使用==和!=
引用類型是可以比較的;
兩個值類型不可以使用==比較,除非重載了==操作符。
如果未被約束,則不能比較,除非重載了==操作符,約束為引用可以比較。
5.泛型類型變量作為操作數使用
不能使用操作符(+,-=,*,<等等)操作泛型類型變量