通過使用泛型,我們可以極大地提高代碼的重用度,同時還可以獲得強類型的支援,避免了隐式的裝箱、拆箱,在一定程度上提升了應用程式的性能。
泛型類執行個體化的理論
C#泛型類在編譯時,先生成中間代碼IL,通用類型T隻是一個占位符。在執行個體化類時,根據使用者指定的資料類型代替T并由即時編譯器(JIT)生成本地代碼,這個本地代碼中已經使用了實際的資料類型,等同于用實際類型寫的類,是以不同的封閉類的本地代碼是不一樣的。按照這個原理,我們可以這樣認為:
泛型類的不同的封閉類是分别不同的資料類型。
為什麼要有泛型
它可以避免重複代碼。如果有幾個類或者方法類似,實作功能相同,差別隻是類型不一樣,如int和string是,這是可以定義一個泛型。
泛型類就類似于一個模闆,可以在需要時為這個模闆傳入任何我們需要的類型。
由一個例子引入:
我們在算法中會學到排序,一個很經典的排序為冒泡排序法。将幾個亂序的數字用冒泡發排序,我想大多數人會很快的寫出來。
我将例子弄詳細一點。建立一張aspx的頁面,放一個button和一個label伺服器控件,輕按兩下button,自動生成響應方法。網頁關鍵代碼如下:
1 <asp:Button ID="Button1" runat="server" Text="對1,8,3,7,2排序" onclick="Button1_Click" />
2 <asp:Label ID="Label1" runat="server" Text=""></asp:Label>
要實作排序,需要現有實作冒泡排序的類,我們建立一個ShortHelper類:
1 public class ShortHelper {
2 public void BubbleSort(int[] array)
3 {
4 int length = array.Length;
5 for(int i=0;i<=length-2;i++)
6 for (int j = length-1; j >= 1; j--)
7 {
8 if (array[j] < array[j - 1])
9 {
10 int temp = array[j];
11 array[j] = array[j - 1];
12 array[j - 1] = temp;
13 }
14 }
15 }
16 }
将相應函數補充完整:
protected void Button1_Click(object sender, EventArgs e)
{
string endsort = "";
ShortHelper shorter = new ShortHelper();
int[] array = { 1, 8, 3, 7, 2 };
shorter.BubbleSort(array);
foreach (int i in array)
{
endsort += i.ToString() + ",";
}
int length = endsort.Length - 1;
endsort = endsort.Substring(0, length);
Label1.Text = endsort;
}
結果如下:

但是,我現在又想對英文字母或者byte進行排序,怎麼辦呢,當然直接修改類型是可以的。但是這樣會造成代碼的不簡潔,重用性差。現在我們就引入泛型,泛型其實就是類似于C++中的模版,是一種解決方法的同法,将類型用一個類似于占位符的 T 代替。修改後的類如下:
1 public class ShortHelper<T> where T : IComparable {
2 public void BubbleSort(T[] array)
3 {
4 int length = array.Length;
5 for (int i = 0; i <= length - 2; i++)
6 for (int j = length - 1; j >= 1; j--)
7 {
8 if (array[j].CompareTo(array[j - 1]) < 0)//因為object是沒有比較大小的方法的,不用IComparable會照成無法比較大小,CompareTo方法用于比較,前一個比後一個小,傳回值<0,反之傳回>0
9 {
10 T temp = array[j];
11 array[j] = array[j - 1];
12 array[j - 1] = temp;
13 }
14 }
15 }
16 }
在網頁中添加
1 <asp:Button ID="Button2" runat="server" Text="對f,a,s,t,v,b,e排序" onclick="Button2_Click" />
2 <asp:Label ID="Label2" runat="server" Text=""></asp:Label>
相應的相應函數為
1 protected void Button1_Click(object sender, EventArgs e)
2 {
3 string endsort = "";
4 ShortHelper<int> shorter = new ShortHelper<int>(); //注意此處
5 int[] array = { 1,8,3,7,2};
6 shorter.BubbleSort(array);
7 foreach (int i in array)
8 {
9 endsort += i.ToString() + ",";
10 }
11 int length = endsort.Length - 1;
12 endsort = endsort.Substring(0, length);
13 Label1.Text = endsort;
14 }
15
16 protected void Button2_Click(object sender, EventArgs e)
17 {
18 string endsort = "";
19 ShortHelper<char> shorter = new ShortHelper<char>(); //注意此處
20 char[] array = { 'f', 'a', 's', 't', 'v', 'b', 'e' };
21 shorter.BubbleSort(array);
22 foreach (char i in array)
23 {
24 endsort += i + ",";
25 }
26 int length = endsort.Length - 1;
27 endsort = endsort.Substring(0, length);
28 Label2.Text = endsort;
29 }
運作結果為:
當然,泛型的用法不僅僅就這,還有泛型接口,泛型方法等等。
優點:
1、性能:避免隐式的裝箱和拆箱
如AarryList(在添加資料時裝箱,讀取時拆箱)和List<T>差別
2、類型安全:
泛型的另一個特性是類型安全。與ArrayList類一樣,如果使用對象,可以在這個集合中添加任意類型。下面的例子在ArrayList類型的集合中添加一個整數、一個字元串和一個MyClass類型的對象:
1 ArrayList list = new ArrayList();
2 list.Add(44);
3 list.Add("mystring");
4 list.Add(new MyClass());
如果這個集合使用下面的foreach語句疊代,而該foreach語句使用整數元素來疊代,編譯器就會編譯這段代碼。但并不是集合中的所有元素都可以轉換為int,是以會出現一個運作異常:
1 foreach (int i in list)
2 {
3 Console.WriteLine(i);
4 }
錯誤應盡早發現。在泛型類List<T>中,泛型類型T定義了允許使用的類型。有了List<int>的定義,就隻能把整數類型添加到集合中。編譯器不會編譯這段代碼,因為Add()方法的參數無效:
1 List<int> list = new List<int>();
2 list.Add(44);
3 list.Add("mystring"); // compile time error
4 list.Add(new MyClass()); // compile time error
3、二進制代碼的重用:
泛型允許更好地重用二進制代碼。泛型類可以定義一次,用許多不同的類型執行個體。
類型參數限制
程式員在編寫泛型類時,總是會對通用資料類型T進行有意或無意地有假想,也就是說這個T一般來說是不能适應所有類型,但怎樣限制調用者傳入的資料類型呢?這就需要對傳入的資料類型進行限制,限制的方式是指定T的祖先,即繼承的接口或類。因為C#的單根繼承性,是以限制可以有多個接口,但最多隻能有一個類,并且類必須在接口之前。
除了可以限制類型參數T 實作某個接口以外,還可以限制T 是一個結構、T 是一個類、T 擁有構造函數、T 繼承自某個基類等。
泛型方法
在泛型方法中,泛型類型用方法聲明來定義。
1 void Swap<T>(ref T x, ref T y)
2 {
3 T temp;
4 temp = x;
5 x = y;
6 y = temp;
7 }
把泛型類型賦予方法調用,就可以調用泛型方法:
1 int i = 4;
2 int j = 5;
3 Swap<int>(ref i, ref j);
但是,因為C#編譯器會通過調用Swap方法來擷取參數的類型,是以不需要把泛型類型賦予方法調用。泛型方法可以像非泛型方法那樣調用:
1 int i = 4;
2 int j = 5;
3 Swap(ref i, ref j);
當一般方法與泛型方法具有相同的簽名時,會覆寫泛型方法。
泛型類的特性
1、預設值:不能把null賦予泛型類型。原因是泛型類型也可以執行個體化為值類型,而null隻能用于引用類型。為了解決這個問題,可以使用default關鍵字。通過default關鍵字,将null賦予引用類型,将0賦予值類型。
T doc = default(T);
2、限制:如果泛型類需要調用泛型類型上的方法,就必須添加限制。
約 束 | 說 明 |
where T : struct | 使用結構限制,類型T必須是值類型 |
where T : class | 類限制指定,類型T必須是引用類型 |
where T : IFoo | 指定類型T必須執行接口IFoo |
where T : Foo | 指定類型T必須派生于基類Foo |
where T : new() | 這是一個構造函數限制,指定類型T必須有一個預設構造函數 |
where T : U | 這個限制也可以指定,類型T1派生于泛型類型T2。該限制也稱為裸類型限制 |
3、繼承:泛型類型可以執行泛型接口,也可以派生于一個類。泛型類可以派生于泛型基類:
1 public class Base<T>
2 {
3 }
4 public class Derived<T> : Base<T>{}
4、靜态成員:泛型類的靜态成員需要特别關注。泛型類的靜态成員隻能在類的一個執行個體中共享。
總結:
泛型。通過泛型類可以建立獨立于類型的類,泛型方法是獨立于類型的方法。增加代碼的重用性。接口、結構和委托也可以用泛型的方式建立。泛型引入了一種新的程式設計方式。
參考資料:
http://www.cnblogs.com/JimmyZhang/archive/2008/12/17/1356727.html,《C#進階程式設計第六版》