程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> .NET實例教程 >> C#中的泛型 (From dotNet SDK 2.0 Beta1)

C#中的泛型 (From dotNet SDK 2.0 Beta1)

編輯:.NET實例教程

泛型(generic)是C#語言2.0和通用語言運行時(CLR)的一個新特性。泛型為.Net框架引入了類型參數(type parameters)的概念。類型參數使得設計類和方法時,不必確定一個或多個具體參數,其的具體參數可延遲到客戶代碼中聲明、實現。這意味著使用泛型的類型參數T,寫一個類MyList<T>,客戶代碼可以這樣調用:MyList<int>, MyList<string>或 MyList<MyClass>。這避免了運行時類型轉換或裝箱操作的代價和風險。









目錄

C#中的泛型. 1

一、泛型概述. 2

二、泛型的優點. 5

三、泛型類型參數. 7

四、類型參數的約束. 8

五、泛型類. 11

六、泛型接口. 13

七、泛型方法. 19

八、泛型委托. 21

九、泛型代碼中的default關鍵字. 23

十、C++模板和C#泛型的區別. 24

十一、運行時中的泛型. 25

十二、基礎類庫中的泛型. 27







一、泛型概述

泛型類和泛型方法兼復用性、類型安全和高效率於一身,是與之對應的非泛型的類和方法所不及。泛型廣泛用於容器(collections)和對容器操作的方法中。.NET框架2.0的類庫提供一個新的命名空間System.Collections.Generic,其中包含了一些新的基於泛型的容器類。要查找新的泛型容器類(collection classes)的示例代碼,請參見基礎類庫中的泛型。當然,你也可以創建自己的泛型類和方法,以提供你自己的泛化的方案和設計模式,這是類型安全且高效的。下面的示例代碼以一個簡單的泛型鏈表類作為示范。(多數情況下,推薦使用由.Net框架類庫提供的List<T>類,而不是創建自己的表。)類型參數T在多處使用,具體類型通常在這些地方來指明表中元素的類型。類型參數T有以下幾種用法:

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 p

arameters.

· 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



--------------------------------------------------------------------------------

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved