泛型(generic)是C#語言2.0和通用語言運行時(CLR)的一個新特性。泛型為.NET框架引入了類型參數(type parameters)的概念。類型參數使得設計類和方法時,不必確定一個或多個具體參數,其的具體參數可延遲到客戶代碼中聲明、實現。這意味著使用泛型的類型參數T,寫一個類MyList ...
泛型(generic)是C#語言2.0和通用語言運行時(CLR)的一個新特性。泛型為.NET框架引入了類型參數(type parameters)的概念。類型參數使得設計類和方法時,不必確定一個或多個具體參數,其的具體參數可延遲到客戶代碼中聲明、實現。這意味著使用泛型的類型參數T,寫一個類MyList<T>,客戶代碼可以這樣調用:MyList<int>, MyList<string>或 MyList<MyClass>。這避免了運行時類型轉換或裝箱操作的代價和風險。
- 泛型概述
泛型類和泛型方法兼復用性、類型安全和高效率於一身,是與之對應的非泛型的類和方法所不及。泛型廣泛用於容器(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>容器里,同樣是把元素加入容器的操作,類似這樣:
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執行替換的詳細信息,請參見運行時中的泛型。