深入理解C#中的泛型(一)
阿新 • • 發佈:2019-01-27
為什麼要有泛型?
請大家思考一個問題:由你來實現一個最簡單的氣泡排序演算法,如果沒有使用泛型的經驗,可能會毫不猶豫的寫出以下程式碼:
現在通過對這個程式進行一個簡單的測試:public class SortHelper { //引數為int陣列的氣泡排序 public void BubbleSort(int[] array) { int length = array.Length; for (int i = 0; i <= length - 2; i++) { for (int j = length - 1; j >= 1; j--) { //對兩個元素進行交換 if (array[j] < array[j - 1]) { int temp = array[j]; array[j] = array[j - 1]; array[j - 1] = temp; } } } } }
我們發現它執行良好,欣喜的認為這便是最好的解決方案了。直到不久後,需要對一個byte型別的陣列進行排序,而上面的排序演算法只能接受一個int型別的陣列。C#是一個強型別的語言,無法在一個接受int陣列型別的地方傳入一個byte陣列。不過沒關係,現在看來最快的解決方法是把程式碼複製一遍,然後將方法的簽名改一下:static void Main(string[] args) { #region 沒有泛型前的演示 SortHelper sorter = new SortHelper(); int[] arrayInt = { 8, 1, 4, 7, 3 }; //對int陣列排序 sorter.BubbleSort(arrayInt); foreach (int i in arrayInt) { Console.Write("{0} ", i); } Console.ReadLine(); #endregion }
好了,再一次解決問題。可通過認證觀察我們發現,這兩個方法除了傳入的引數型別不同外,方法的實現很相似,是可以進行進一步抽象的,於是我們思考,為什麼在定義引數的時候不用一個佔位符T代替呢?T是Type的縮寫,可以代表任何型別,這樣就可以遮蔽兩個方法簽名的差異:public class SortHelper { //引數為byte陣列的氣泡排序 public void BubbleSort(byte[] array) { int length = array.Length; for (int i = 0; i <= length - 2; i++) { for (int j = length - 1; j >= 1; j--) { //對兩個元素進行交換 if (array[j] < array[j - 1]) { byte temp = array[j]; array[j] = array[j - 1]; array[j - 1] = temp; } } } } }
public class SortHelper<T>
{
//引數為T的氣泡排序
public void BubbleSort(T[] array)
{
int length = array.Length;
for (int i = 0; i <= length - 2; i++)
{
for (int j = length - 1; j >= 1; j--)
{
//對兩個元素進行交換
if (array[j] < array[j - 1])
{
T temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
}
}
}
}
通過程式碼我們可以發現,使用泛型極大的減少了重複程式碼,使程式碼更加清爽。泛型類就類似於一個模板,可以再需要時為這個模板傳入任何需要的型別。現在更專業些,為佔位符T起一個正式的名稱,在.Net中叫做“型別引數”。
型別引數約束
實際上,如果執行上面的程式碼就會發現,它連編譯都通不過去,為什麼呢?考慮這樣一個問題:假設我們自定義一個型別,名字叫做Book,它包含兩個欄位:一個是int型別的Price代表書的價格;一個是string型別的Title,代表書的標題:
public class Book
{
//價格欄位
private int price;
//標題欄位
private string title;
//建構函式
public Book() { }
public Book(int price, string title)
{
this.price = price;
this.title = title;
}
//價格屬性
public int Price
{
get { return this.price; }
}
//標題屬性
public string Titie
{
get { return this.title; }
}
}
現在建立一個Book型別的陣列,然後使用上面定義的泛型類對它進行排序,程式碼應該像下面這樣: Book[] bookArray = new Book[2];
Book book1 = new Book(30, "HTML5解析");
Book book2 = new Book(21, "JavaScript實戰");
bookArray[0] = book1;
bookArray[1] = book2;
SortHelper<Book> sorterGeneric = new SortHelper<Book>();
sorterGeneric.BubbleSort(bookArray);
foreach (Book b in bookArray)
{
Console.WriteLine("Price:{0}", b.Price);
Console.WriteLine("Title:{0}", b.Titie);
}
這時問題來了,既然是排序,就免不了比較大小,那麼現在請問:book1和book2誰比較大?張三可以說book1大,因為它的Price是30,;而李四可以說book2大,因為它的Title是“J”開頭的,比book1的“H”靠後。說了半天,問題在於不確定按什麼規則排序。既然不知道,那我們就給Book定義一種排序規則(按價格排序),我們宣告一個用於比較的介面:
public interface IComparable
{
int CompareTo(object obj);
}
讓Book型別實現這個介面:public class Book : IComparable
{
//CODE:上面的實現略
public int CompareTo(object obj)
{
Book book2 = (Book)obj;
return this.Price.CompareTo(book2.Price);
}
}
既然我們現在已經讓Book類實現了IComparable介面,那麼泛型類應該可以工作了吧?不行的,還要記得,泛型類是一個模板類,它對在執行時傳遞的型別引數是一無所知的,也不會做任何的猜測,所以需要我們告訴泛型類SortHelper<T>,它所接受的T型別引數必須能夠進行比較,也就是說必須實現IComparable介面,我們把對T進行約束這種行為稱:泛型引數約束。
為了要求型別引數T必須實現IComparable介面,需要像下面這樣重新定義:
public class SortHelper<T> where T : IComparable
{
//引數為T的氣泡排序
public void BubbleSort(T[] array)
{
int length = array.Length;
for (int i = 0; i <= length - 2; i++)
{
for (int j = length - 1; j >= 1; j--)
{
//對兩個元素進行交換
if (array[j].CompareTo(array[j - 1]) < 0)
{
T temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
}
}
}
}
}
上面的定義說明了型別引數T必須實現IComparable介面,否則將無法通過編譯。因為現在T已經實現了IComparable介面,而陣列array中的成員是T的例項,所以當在array[i]後面點選小數點“.”時,VS可以智慧提醒出T是IComparable的成員,也就是CompareTo()方法。