泛型資料整理
泛型(generic)是C#語言2.0和通用語言執行時(CLR)的一個新特性。泛型為.NET框架引入了型別引數(type parameters)的概念。型別引數使得設計類和方法時,不必確定一個或多個具體引數,其的具體引數可延遲到客戶程式碼中宣告、實現。這意味著使用泛型的型別引數T,寫一個類MyList<T>,客戶程式碼可以這樣呼叫:MyList<int>, MyList<string>或 MyList<MyClass>。這避免了執行時型別轉換或裝箱操作的代價和風險。
目錄
C# 中的泛型
一、泛型概述. 2
二、泛型的優點. 5
三、泛型型別引數. 7
四、型別引數的約束. 8
五、泛型類. 11
六、泛型介面. 13
七、泛型方法. 19
八、泛型委託. 21
九、泛型程式碼中的default 關鍵字. 23
十、C++ 模板和C# 泛型的區別. 24
十一 、執行時中的泛型. 25
十二 、基礎類庫中的泛型. 27
一、泛型概述
泛型類和泛型方法兼複用性、型別安全和高效率於一身,是與之對應的非泛型的類和方法所不及。泛型廣泛用於容器(
l 在AddHead方法中,作為方法引數的型別。
l 在公共方法GetNext中,以及巢狀類Node的 Data屬性中作為返回值的型別。
l 在巢狀類中,作為私有成員data的型別。
注意一點,T對巢狀的類Node也是有效的。當用一個具體類來實現MyList<T>時——如MyList<int>——每個出現過的T都要用int代替。
using System;
using System.Collections.Generic;
public class MyList<T> //type parameter T in angle brackets
{
private Node head;
// The nested type is also generic on T.
private class Node
{
private Node next;
//T as private member data type:
private T data;
//T used in non-generic constructor:
public Node(T t)
{
next = null;
data = t;
}
public Node Next
{
get { return next; }
set { next = value; }
}
//T as return type of property:
public T Data
{
get { return data; }
set { data = value; }
}
}
public MyList()
{
head = null;
}
//T as method parameter type:
public void AddHead(T t)
{
Node n = new Node(t);
n.Next = head;
head = n;
}
public IEnumerator<T> GetEnumerator()
{
Node current = head;
while (current != null)
{
yield return current.Data;
current = current.Next;
}
}
}
下面的示例程式碼演示了客戶程式碼如何使用泛型類MyList<T>,來建立一個整數表。通過簡單地改變引數的型別,很容易改寫下面的程式碼,以建立字串或其他自定義型別的表。
class Program
{
static void Main(string[] args)
{
//int is the type argument.
MyList<int> list = new MyList<int>();
for (int x = 0; x < 10; x++)
list.AddHead(x);
foreach (int i in list)
{
Console.WriteLine(i);
}
Console.WriteLine("Done");
}
}
二、泛型的優點
針對早期版本的通用語言執行時和C#語言的侷限,泛型提供了一個解決方案。以前型別的泛化(generalization)是靠型別與全域性基類System.Object的相互轉換來實現。.NET框架基礎類庫的ArrayList容器類,就是這種侷限的一個例子。ArrayList是一個很方便的容器類,使用中無需更改就可以儲存任何引用型別或值型別。
//The .NET Framework 1.1 way of creating a list
ArrayList list1 = new ArrayList();
list1.Add(3);
list1.Add(105);
//...
ArrayList list2 = new ArrayList();
list2.Add(“It is raining in Redmond.”);
list2.Add("It is snowing in the mountains.");
//...
但是這種便利是有代價的,這需要把任何一個加入ArrayList的引用型別或值型別都隱式地向上轉換成System.Object。如果這些元素是值型別,那麼當加入到列表中時,它們必須被裝箱;當重新取回它們時,要拆箱。型別轉換和裝箱、拆箱的操作都降低了效能;在必須迭代(iterate)大容器的情況下,裝箱和拆箱的影響可能十分顯著。
另一個侷限是缺乏編譯時的型別檢查,當一個ArrayList把任何型別都轉換為Object,就無法在編譯時預防客戶程式碼類似這樣的操作:
ArrayList list = new ArrayList();
//Okay.
list.Add(3);
//Okay, but did you really want to do this?
list.Add(.“It is raining in Redmond.”);
int t = 0;
//This causes an InvalidCastException to be returned.
foreach(int x in list)
{
t += x;
}
雖然這樣完全合法,並且有時是有意這樣建立一個包含不同型別元素的容器,但是把string和int變數放在一個ArrayList中,幾乎是在製造錯誤,而這個錯誤直到執行的時候才會被發現。
在1.0版和1.1版的C#語言中,你只有通過編寫自己的特定型別容器,才能避免.NET框架類庫的容器類中泛化程式碼(generalized code)的危險。當然,因為這樣的類無法被其他的資料型別複用,也就失去泛型的優點,你必須為每個需要儲存的型別重寫該類。
ArrayList和其他相似的類真正需要的是一種途徑,能讓客戶程式碼在例項化之前指定所需的特定資料型別。這樣就不需要向上型別轉換為Object,而且編譯器可以同時進行型別檢查。換句話說,ArrayList需要一個型別引數。這正是泛型所提供的。在System.Collections.Generic名稱空間中的泛型List<T>容器裡,同樣是把元素加入容器的操作,類似這樣:
The .NET Framework 2.0 way of creating a list
List<int> list1 = new List<int>();
//No boxing, no casting:
list1.Add(3);
//Compile-time error:
list1.Add("It is raining in Redmond.");
與ArrayList相比,在客戶程式碼中唯一增加的List<T>語法是宣告和例項化中的型別引數。程式碼略微複雜的回報是,你建立的表不僅比ArrayList更安全,而且明顯地更加快速,尤其當表中的元素是值型別的時候。
三、泛型型別引數
在泛型型別或泛型方法的定義中,型別引數是一個佔位符(placeholder),通常為一個大寫字母,如T。在客戶程式碼宣告、例項化該型別的變數時,把T替換為客戶程式碼所指定的資料型別。泛型類,如泛型概述中給出的MyList<T>類,不能用作as-is,原因在於它不是一個真正的型別,而更像是一個型別的藍圖。要使用MyList<T>,客戶程式碼必須在尖括號內指定一個型別引數,來宣告並例項化一個已構造型別(constructed type)。這個特定類的型別引數可以是編譯器識別的任何型別。可以建立任意數量的已構造型別例項,每個使用不同的型別引數,如下:
MyList<MyClass> list1 = new MyList<MyClass>();
MyList<float> list2 = new MyList<float>();
MyList<SomeStruct> list3 = new MyList<SomeStruct>();
在這些MyList<T>的例項中,類中出現的每個T都將在執行的時候被型別引數所取代。依靠這樣的替換,我們僅用定義類的程式碼,就建立了三個獨立的型別安全且高效的物件。有關CLR執行替換的詳細資訊,請參見執行時中的泛型。
四、型別引數的約束
若要檢查表中的一個元素,以確定它是否合法或是否可以與其他元素相比較,那麼編譯器必須保證:客戶程式碼中可能出現的所有型別引數,都要支援所需呼叫的操作或方法。這種保證是通過在泛型類的定義中,應用一個或多個約束而得到的。一個約束型別是一種基類約束,它通知編譯器,只有這個型別的物件或從這個型別派生的物件,可被用作型別引數。一旦編譯器得到這樣的保證,它就允許在泛型類中呼叫這個型別的方法。上下文關鍵字where用以實現約束。下面的示例程式碼說明了應用基類約束,為MyList<T>類增加功能。
public class Employee
{
public class Employee
{
private string name;
private int id;
public Employee(string s, int i)
{
name = s;
id = i;
}
public string Name
{
get { return name; }
set { name = value; }
}
public int ID
{
get { return id; }
set { id = value; }
}
}
}
class MyList<T> where T: Employee
{
//Rest of class as before.
public T FindFirstOccurrence(string s)
{
T t = null;
Reset();
while (HasItems())
{
if (current != null)
{
//The constraint enables this:
if (current.Data.Name == s)
{
t = current.Data;
break;
}
else
{
current = current.Next;
}
} //end if
} // end while
return t;
}
}
約束使得泛型類能夠使用Employee.Name屬性,因為所有為型別T的元素,都是一個Employee物件或是一個繼承自Employee的物件。
同一個型別引數可應用多個約束。約束自身也可以是泛型類,如下:
class MyList<T> where T: Employee, IEmployee, IComparable<T>, new()
{…}
下表列出了五類約束:
約束 |
描述 |
where T: struct |
型別引數必須為值型別。 |
where T : class |
型別引數必須為引用型別。 |
where T : new() |
型別引數必須有一個公有、無參的建構函式。當於其它約束聯合使用時,new()約束必須放在最後。 |
where T : <base class name> |
型別引數必須是指定的基型別或是派生自指定的基型別。 |
where T : <interface name> |
型別引數必須是指定的介面或是指定介面的實現。可以指定多個介面約束。介面約束也可以是泛型的。 |
型別引數的約束,增加了可呼叫的操作和方法的數量。這些操作和方法受約束型別及其派生層次中的型別的支援。因此,設計泛型類或方法時,如果對泛型成員執行任何賦值以外的操作,或者是呼叫System.Object中所沒有的方法,就需要在型別引數上使用約束。
無限制型別引數的一般用法
沒有約束的型別引數,如公有類MyClass<T>{...}中的T, 被稱為無限制型別引數(unbounded type parameters)。無限制型別引數有以下規則:
l 不能使用運算子 != 和 == ,因為無法保證具體的型別引數能夠支援這些運算子。
l 它們可以與System.Object相互轉換,也可顯式地轉換成任何介面型別。
l 可以與null比較。如果一個無限制型別引數與null比較,當此型別引數為值型別時,比較的結果總為false。
無型別約束
當約束是一個泛型型別引數時,它就叫無型別約束(Naked type constraints)。當一個有型別引數成員方法,要把它的引數約束為其所在類的型別引數時,無型別約束很有用。如下例所示:
class List<T>
{
//...
void Add<U>(List<U> items) where U:T {…}
}
在上面的示例中, Add方法的上下文中的T,就是一個無型別約束;而List類的上下文中的T,則是一個無限制型別引數。
無型別約束也可以用在泛型類的定義中。注意,無型別約束一定也要和其它型別引數一起在尖括號中宣告:
//naked type constraint
public class MyClass<T,U,V> where T : V
因為編譯器只認為無型別約束是從System.Object繼承而來,所以帶有無型別約束的泛型類的用途十分有限。當你希望強制兩個型別引數具有繼承關係時,可對泛型類使用無型別約束。
五、泛型類
泛型類封裝了不針對任何特定資料型別的操作。泛型類常用於容器類,如連結串列、雜湊表、棧、佇列、樹等等。這些類中的操作,如對容器新增、刪除元素,不論所儲存的資料是何種型別,都執行幾乎同樣的操作。
對大多數情況,推薦使用.NET框架2.0類庫中所提供的容器類。有關使用這些類的詳細資訊,請參見基礎類庫中的泛型。
通常,從一個已有的具體類來建立泛型類,並每次把一個型別改為型別引數,直至達到一般性和可用性的最佳平衡。當建立你自己的泛型類時,需要重點考慮的事項有:
l 哪些型別應泛化為型別引數。一般的規律是,用引數表示的型別越多,程式碼的靈活性和複用性也就越大。過多的泛化會導致程式碼難以被其它的開發人員理解。
l 如果有約束,那麼型別引數需要什麼樣約束。一個良好的習慣是,儘可能使用最大的約束,同時保證可以處理所有需要處理的型別。例如,如果你知道你的泛型類只打算使用引用型別,那麼就應用這個類的約束。這樣可以防止無意中使用值型別,同時可以對T使用as運算子,並且檢查空引用。
l 把泛型行為放在基類中還是子類中。泛型類可以做基類。同樣非泛型類的設計中也應考慮這一點。泛型基類的繼承規則 。
l 是否實現一個或多個泛型介面。例如,要設計一個在基於泛型的容器中建立元素的類,可能需要實現類似IComparable<T>的介面,其中T是該類的引數。
泛型概述中有一個簡單泛型類的例子。
型別引數和約束的規則對於泛型類的行為(behavior)有一些潛在的影響,——尤其是對於繼承和成員可訪問性。在說明這個問題前,理解一些術語十分重要。對於一個泛型類Node<T>,客戶程式碼既可以通過指定一個型別引數來建立一個封閉構造型別(Node<int>),也可以保留型別引數未指定,例如指定一個泛型基類來建立開放構造型別(Node<T>)。泛型類可以繼承自具體類、封閉構造型別或開放構造型別:
// concrete type
class Node<T> : BaseNode
//closed constructed type
class Node<T> : BaseNode<int>
//open constructed type
class Node<T> : BaseNode<T>
非泛型的具體類可以繼承自封閉構造基類,但不能繼承自開放構造基類。這是因為客戶程式碼無法提供基類所需的型別引數。
//No error.
class Node : BaseNode<int>
//Generates an error.
class Node : BaseNode<T>
泛型的具體類可以繼承自開放構造型別。除了與子類共用的型別引數外,必須為所有的型別引數指定型別,如下程式碼所示:
//Generates an error.
class Node<T> : BaseNode<T, U> {…}
//Okay.
class Node<T> : BaseNode<T, int>{…}
繼承自開放結構型別的泛型類,必須指定:
Generic classes that inherit from open constructed types must specify must specify constraints that are a superset of, or imply, the constraints on the base type:
class NodeItem<T> where T : IComparable<T>, new() {…}
class MyNodeItem<T> : NodeItem<T> where T : IComparable<T> , new(){…}
泛型型別可以使用多種型別引數和約束,如下:
class KeyType<K,V>{…}
class SuperKeyType<K,V,U> where U : IComparable<U>, where V : new(){…}
開放結構和封閉構造型別型可以用作方法的引數:
void Swap<T>(List<T> list1, List<T> list2){…}
void Swap(List<int> list1, List<int> list2){…}
六、泛型介面
不論是為泛型容器類,還是表示容器中元素的泛型類,定義介面是很有用的。把泛型介面與泛型類結合使用是更好的用法,比如用IComparable<T>而非IComparable,以避免值型別上的裝箱和拆箱操作。.NET框架2.0類庫定義了幾個新的泛型介面,以配合System.Collections.Generic中新容器類的使用。
當一個介面被指定為型別引數的約束時,只有實現該介面的型別可被用作型別引數。下面的示例程式碼顯示了一個從MyList<T>派生的SortedList<T>類。更多資訊,請參見泛型概述。SortedList<T>增加了約束where T : IComparable<T>。
這使得SortedList<T>中的BubbleSort方法可以使用表中的元素的IComparable<T>.CompareTo方法。在這個例子中,表中的元素是簡單類——實現IComparable<Person>的Person類。
using System;
using System.Collections.Generic;
//Type parameter T in angle brackets.
public class MyList<T>
{
protected Node head;
protected Node current = null;
// Nested type is also generic on T
protected class Node
{
public Node next;
//T as private member datatype.
private T data;
//T used in non-generic constructor.
public Node(T t)
{
next = null;
data = t;
}
public Node Next
{
get { return next; }
set { next = value; }
}
//T as return type of property.
public T Data
{
get { return data; }
set { data = value; }
}
}
public MyList()
{
head = null;
}
//T as method parameter type.
public void AddHead(T t)
{
Node n = new Node(t);
n.Next = head;
head = n;
}
// Implement IEnumerator<T> to enable foreach
// iteration of our list. Note that in C# 2.0
// you are not required to implment Current and
// GetNext. The compiler does that for you.
public IEnumerator<T> GetEnumerator()
{
Node current = head;
while (current != null)
{
yield return current.Data;
current = current.Next;
}
}
}
public class SortedList<T> : MyList<T> where T : IComparable<T>
{
// A simple, unoptimized sort algorithm that
// orders list elements from lowest to highest:
public void BubbleSort()
{
if (null == head || null == head.Next)
return;
bool swapped;
do
{
Node previous = null;
Node current = head;
swapped = false;
while (current.next != null)
{
// Because we need to call this method, the SortedList
// class is constrained on IEnumerable<T>
if (current.Data.CompareTo(current.next.Data) > 0)
{
Node tmp = current.next;
current.next = current.next.next;
tmp.next = current;
if (previous == null)
{
head = tmp;
}
else
{
previous.next = tmp;
}
previous = tmp;
swapped = true;
}
else
{
previous = current;
current = current.next;
}
}// end while
} while (swapped);
}
}
// A simple class that implements IComparable<T>
// using itself as the type argument. This is a
// common design pattern in objects that are
// stored in generic lists.
public class Person : IComparable<Person>
{
string name;
int age;
public Person(string s, int i)
{
name = s;
age = i;
}
// This will cause list elements
// to be sorted on age values.
public int CompareTo(Person p)
{
return age - p.age;
}
public override string ToString()
{
return name + ":" + age;
}
// Must implement Equals.
public bool Equals(Person p)
{
return (this.age == p.age);
}
}
class Program
{
static void Main(string[] args)
{
//Declare and instantiate a new generic SortedList class.
//Person is the type argument.
SortedList<Person> list = new SortedList<Person>();
//Create name and age values to initialize Person objects.
string[] names = new string[]{"Franscoise", "Bill", "Li", "Sandra", "Gunnar", "Alok", "Hiroyuki", "Maria", "Alessandro", "Raul"};
int[] ages = new int[]{45, 19, 28, 23, 18, 9, 108, 72, 30, 35};
//Populate the list.
for (int x = 0; x < 10; x++)
{
list.AddHead(new Person(names[x], ages[x]));
}
//Print out unsorted list.
foreach (Person p in list)
{
Console.WriteLine(p.ToString());
}
//Sort the list.
list.BubbleSort();
//Print out sorted list.
foreach (Person p in list)
{
Console.WriteLine(p.ToString());
}
Console.WriteLine("Done");
}
}
可以在一個型別指定多個介面作為約束,如下:
class Stack<T> where T : IComparable<T>, IMyStack1<T>{}
一個介面可以定義多個型別引數,如下:
IDictionary<K,V>
介面和類的繼承規則相同:
//Okay.
IMyInterface : IBaseInterface<int>
//Okay.
IMyInterface<T> : IBaseInterface<T>
//Okay.
IMyInterface<T>: IBaseInterface<int>
//Error.
IMyInterface<T> : IBaseInterface2<T, U>
具體類可以實現封閉構造介面,如下:
class MyClass : IBaseInterface<string>
泛型類可以實現泛型介面或封閉構造介面,只要類的引數列表提供了介面需要的所有引數,如下:
//Okay.
class MyClass<T> : IBaseInterface<T>
//Okay.
class MyClass<T> : IBaseInterface<T, string>
泛型類、泛型結構,泛型介面都具有同樣方法過載的規則。詳細資訊,請參見泛型方法。
七、泛型方法
泛型方法是聲名了型別引數的方法,如下:
void Swap<T>( ref T lhs, ref T rhs)
{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}
下面的示例程式碼顯示了一個以int作為型別引數,來呼叫方法的例子:
int a = 1;
int b = 2;
//…
Swap<int>(a, b);
也可以忽略型別引數,編譯器會去推斷它。下面呼叫Swap的程式碼與上面的例子等價:
Swap(a, b);
靜態方法和例項方法有著同樣的型別推斷規則。編譯器能夠根據傳入的方法引數來推斷型別引數;而無法單獨根據約束或返回值來判斷。因此型別推斷對沒有引數的方法是無效的。型別推斷髮生在編譯的時候,且在編譯器解析過載方法標誌之前。編譯器對所有同名的泛型方法應用型別推斷邏輯。在決定(resolution)過載的階段,編譯器只包含那些型別推斷成功的泛型類。更多資訊,請參見C# 2.0規範,20.6.4型別引數推斷
在泛型方法中,非泛型方法能訪問所在類中的型別引數,如下:
class MyClass<T>
{
//…
void Swap (ref T lhs, ref T rhs){…}
}
[JX1] 定義一個泛型方法,和其所在的類具有相同的型別引數;試圖這樣做,編譯器會產生警告CS0693。
class MyList<T>
{
// CS0693
void MyMethod<T>{...}
}
class MyList<T>
{
//This is okay, but not common.
void SomeMethod<U>(){...}
}
使用約束可以在方法中使用更多的型別引數的特定方法。這個版本的Swap<T>稱為SwapIfGreater<T>,它只能使用實現了IComparable<T>的型別引數。
void SwapIfGreater<T>( ref T lhs, ref T rhs) where T: IComparable<T>
{
T temp;
if(lhs.CompareTo(rhs) > 0)
{
temp = lhs;
lhs = rhs;
rhs = temp;
}
}
泛型方法通過多個型別引數來過載。例如,下面的這些方法可以放在同一個類中:
void DoSomething(){}
void DoSomething<T>(){}
void DoSomething<T,U>(){}
八、泛型委託
無論是在類定義內還是類定義外,委託可以定義自己的型別引數。引用泛型委託的程式碼可以指定型別引數來建立一個封閉構造型別,這和例項化泛型類或呼叫泛型方法一樣,如下例所示:
public delegate void MyDelegate<T>(T item);
public void Notify(int i){}
//...
MyDelegate<int> m = new MyDelegate<int>(Notify);
C#2.0版有個新特性稱為方法組轉換(method group conversion),具體代理和泛型代理型別都可以使用。用方法組轉換可以把上面一行寫做簡化語法:
MyDelegate<int> m = Notify;
在泛型類中定義的委託,可以與類的方法一樣地使用泛型類的型別引數。
class Stack<T>
{
T[] items;
int index
//...
public delegate void StackDelegate(T[] items);
}
引用委託的程式碼必須要指定所在類的型別引數,如下:
Stack<float> s = new Stack<float>();
Stack<float>.StackDelegate myDelegate = StackNotify;
泛型委託在定義基於典型設計模式的事件時特別有用。因為sender[JX2] ,而再也不用與Object相互轉換。
public void StackEventHandler<T,U>(T sender, U eventArgs);
class Stack<T>
{
//…
public class StackEventArgs : EventArgs{...}
public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent;
protected virtual void OnStackChanged(StackEventArgs a)
{
stackEvent(this, a);
}
}
class MyClass
{
public static void HandleStackChange<T>(Stack<T> stack, StackEventArgs args){...};
}
Stack<double> s = new Stack<double>();
MyClass mc = new MyClass();
s.StackEventHandler += mc.HandleStackChange;
九、泛型程式碼中的 default 關鍵字
在泛型類和泛型方法中會出現的一個問題是,如何把預設值賦給引數化型別,此時無法預先知道以下兩點:
l T將是值型別還是引用型別
l 如果T是值型別,那麼T將是數值還是結構
對於一個引數化型別T的變數t,僅當T是引用型別時,t = null語句才是合法的; t = 0只對數值的有效,而對結構則不行。這個問題的解決辦法是用default關鍵字,它對引用型別返回空,對值型別的數值型返回零。而對於結構,它將返回結構每個成員,並根據成員是值型別還是引用型別,返回零或空。下面MyList<T>類的例子顯示瞭如何使用default關鍵字。更多資訊,請參見泛型概述。
public class MyList<T>
{
//...
public T GetNext()
{
T temp = default(T);
if (current != null)
{
temp = current.Data;
current = current.Next;
}
return temp;
}
}
十、 C++ 模板和 C# 泛型的區別
(未翻譯)
C# Generics and C++ templates are both language features that provide support for parameterized types. However, there are many differences between the two. At the syntax level, C# generics are a simpler approach to parameterized types without the complexity of C++ templates. In addition, C# does not attempt to provide all of the functionality that C++ templates provide. At the implementation level, the primary difference is that C# generic type substitutions are performed at runtime and generic type information is thereby preserved for instantiated objects. For more information, see Generics in the Runtime.
The following are the key differences between C# Generics and C++ templates:
· C# generics do not provide the same amount of flexibility as C++ templates. For example, it is not possible to call arithmetic operators in a C# generic class, although it is possible to call user defined operators.
· C# does not allow non-type template parameters, such as template C<int i> {}.
· C# does not support explicit specialization; that is, a custom implementation of a template for a specific type.
· C# does not support partial specialization: a custom implementation for a subset of the type arguments.
· C# does not allow the type parameter to be used as the base class for the generic type.
· C# does not allow type parameters to have default types.
· In C#, a generic type parameter cannot itself be a generic, although constructed types can be used as generics. C++ does allow template parameters.
· C++ allows code that might not be valid for all type parameters in the template, which is then checked for the specific type used as the type parameter. C# requires code in a class to be written in such a way that it will work with any type that satisfies the constraints. For example, in C++ it is possible to write a function that uses the arithmetic operators + and - on objects of the type parameter, which will produce an error at the time of instantiation of the template with a type that does not support these operators. C# disallows this; the only language constructs allowed are those that can be deduced from the constraints.
十一 、執行時中的泛型
Specialized generic types are created once for each unique value type used as a parameter.
當泛型類或泛型方法被編譯為微軟中間語言(MSIL)後,它所包含的元資料定義了它的型別引數。根據所給的型別引數是值型別還是引用型別,對泛型型別所用的MSIL也是不同的。
當第一次以值型別作為引數來構造一個泛型型別,執行時用所提供的引數或在MSIL中適當位置被替換的引數,來建立一個專用的泛型型別。[JX3]
例如,假設你的程式程式碼聲名一個由整型構成的棧,如:
Stack<int> stack;
此時,執行時用整型恰當地替換了它的型別引數,生成一個專用版本的棧。此後,程式程式碼再用到整型棧時,執行時複用已建立的專用的棧。下面的例子建立了兩個整型棧的例項,它們共用一個Stack<int>程式碼例項:
Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();
然而,如果由另一種值型別——如長整型或使用者自定義的結構——作為引數,在程式碼的其他地方建立另一個棧,那麼執行時會生成另一個版本的泛型型別。這次是把長整型替換到MSIL中的適當的位置。由於每個專用泛型類原本就包含值型別,因此不需要再轉換。
對於引用型別,泛型的工作略有不同。當第一次用任何引用型別構造泛型類時,執行時在MSIL中建立一個專用泛型類,其中的引數被物件引用所替換。之後,每當用一個引用型別作為引數來例項化一個已構造型別時,就忽略其型別,執行時複用先前建立的專用版本的泛型類。這可能是由於所有的引用的大小都相同。
例如,假如你有兩個引用型別,一個Customer類和一個Order類;進一步假設你建立了一個Customer的棧:
Stack<Customer> customers;
此時,執行時生成一個專用版本的棧,用於稍後儲存物件的引用,而不是儲存資料。假如下一行程式碼建立了一個另一種引用型別的棧,名為Order:
Stack<Order> orders = new Stack<Order>();
和值型別不同,執行時並沒有為Order型別建立另一個棧的專用版本。相反,執行時建立了一個專用版本棧例項,並且變數orders指向這個例項。如果之後是一行建立Customer型別的棧的程式碼:
customers = new Stack<Customer>();
和之前以Order型別建立的棧一樣,建立了專用棧的另一個例項,並且其中所包含的指標指向一塊大小與Customer類一致的記憶體。由於不同程式間引用型別的數量差別很大,而編譯器只為引用型別的泛型類建立一個專用類,因此C#對泛型的實現極大地降低了程式碼膨脹。
此外,當用型別引數實現一個泛型C#類時,想知道它是指型別還是引用型別,可以在執行時通過反射確定它的真實型別和它的型別引數。
十二 、基礎類庫中的泛型
2.0版的.NET框架類庫提供了一個新的名稱空間,System.Collections.Generic,其中包含了一些已經可以使用的泛型容器類和相關的介面。和早期版本的.NET框架提供的非泛型容器類相比,這些類和介面更高效且是型別安全的。在設計、實現自定義的容器類之前,請你考慮是否使用或繼承所列出類中的一個。
下面的表格列出了新的泛型類和介面,旁邊是對應的非泛型類和介面。在一些地方要特別注意,如List<T>和Dictionary<T>,新泛型類的行為(behavior)與它們所替換的非泛型類有些不同,也不完全相容。更詳細的內容,請參考System.Collections.Generic的文件
泛型類或介面 |
描述 |
對應的非泛型型別 |
Collection<T> ICollection<T> |
為泛型容器提供基類 |
CollectionBase ICollection |
Comparer<T> IComparer<T> IComparable<T> |
比較兩個相同泛型型別的物件是否相等、可排序。 |
Comparer IComparer IComparable |
Dictionary<K, V> IDictionary<K,V> |
表示用鍵組織的鍵/值對集合。 |
Hashtable IDictionary |
Dictionary<K, V>.KeyCollection |
表示Dictionary<K, V>中鍵的集合。 |
None. |
Dictionary<K, V>.ValueCollection |
表示Dictionary<K, V>中值的集合。 |
None. |
IEnumerable<T> IEnumerator<T> |
表示可以使用foreach 迭代的集合。 |
IEnumerable IEnumerator |
KeyedCollection<T, U> |
表示有鍵值的集合。 |
KeyedCollection |
LinkedList<T> |
表示雙向連結串列。 |
None. |
LinkedListNode<T> |
表示LinkedList<T>中的節點。 |
None. |
List<T> IList<T> |
使用大小可按需動態增加的陣列實現 IList 介面 |
ArrayList IList |
Queue<T> |
表示物件的先進先出集合。 |
Queue |
ReadOnlyCollection<T> |
為泛型只讀容器提供基類。 |
ReadOnlyCollectionBase |
SortedDictionary<K, V> |
表示鍵/值對的集合,這些鍵和值按鍵排序並可按照鍵訪問,實現IComparer<T>介面。 |
SortedList |
Stack<T> |
表示物件的簡單的後進先出集合。 |
Stack |