1. 程式人生 > >深入理解C#中的泛型(一)

深入理解C#中的泛型(一)

為什麼要有泛型?

  請大家思考一個問題:由你來實現一個最簡單的氣泡排序演算法,如果沒有使用泛型的經驗,可能會毫不猶豫的寫出以下程式碼:

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;
                    }
                }
            }
        }
    }
  現在通過對這個程式進行一個簡單的測試:
      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
        }
  我們發現它執行良好,欣喜的認為這便是最好的解決方案了。直到不久後,需要對一個byte型別的陣列進行排序,而上面的排序演算法只能接受一個int型別的陣列。C#是一個強型別的語言,無法在一個接受int陣列型別的地方傳入一個byte陣列。不過沒關係,現在看來最快的解決方法是把程式碼複製一遍,然後將方法的簽名改一下:
 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;
                    }
                }
            }
        }
    }
  好了,再一次解決問題。可通過認證觀察我們發現,這兩個方法除了傳入的引數型別不同外,方法的實現很相似,是可以進行進一步抽象的,於是我們思考,為什麼在定義引數的時候不用一個佔位符T代替呢?T是Type的縮寫,可以代表任何型別,這樣就可以遮蔽兩個方法簽名的差異:    
   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()方法。