泛型:支持值類型和引用類型,不支持枚舉。
沒有泛型屬性。
泛型的好處:
源代碼保護。使用泛型算法不需要訪問算法的源碼——相對於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.泛型類型變量作為操作數使用
不能使用操作符(+,-=,*,<等等)操作泛型類型變量