標籤:
在C# 2.0中引入了泛型,泛型的出現解決了編碼中的很多問題。相信大家一定經常用到"System.Collections.Generic"命名空間中的泛型集合類("Generic"就是泛型的意思)。在C# 1.0中,我們還在使用"System.Collections"命名空間中的非泛型集合類,那麼看看我們在沒有泛型的時候遇到的問題。
問題1:強制類型轉換
ArrayList stuList = new ArrayList();Student wilber = new Student { Name = "Wilber", Age = 27, Gender = "Male" };stuList.Add(wilber);Student stu = (Student)stuList[0];stuList.Add(10);
在使用非泛型集合ArrayList時,所有的對象都是以object類型加入ArrayList,當對象從ArrayList取出的時候也是object類型,這時我們就需要進行強制類型轉換,如果轉換不當,就會得到一個運行時的錯誤;即使我們向ArrayList添加不同類型的對象時,也不會報錯(例如上面向stuList中加入了一個int值)。
問題2:裝箱和拆箱
在上面的例子中,如果我們使用ArrayList存放一組實值型別的資料(例如一組int值),存入時,每個實值型別的資料都要進行裝箱為object類型;取出時,每個object類型的資料又要進行拆箱操作。
可以看到,在使用非泛型集合的時候,使用者需要自己進行類型轉換,並且可能遇到運行時的類型轉換異常;同時,對於實值型別的操作 ,非泛型集合會有裝箱和拆箱帶來的效率問題。
泛型的出現
對於上面的問題,我們可以使用C# 2.0中的泛型集合。
這樣一來,我們就通過型別參數(例子中的Student)來限制List可以包含的執行個體類型,從而避免的強制類型轉換。
同時,通過型別參數,編譯器可以進行類型檢查,當試圖往List中存入一個與型別參數不匹配的對象的時候,編譯器就是給出錯誤提示。
List<Student> stuList = new List<Student>();Student wilber = new Student { Name = "Wilber", Age = 27, Gender = "Male" };stuList.Add(wilber);Student stu = stuList[0];stuList.Add(10);
泛型中的術語
下面我們看看泛型中的一些概念和術語。
泛型有兩種表現形式:泛型型別(包括類、介面、委託和結構,沒有泛型枚舉)和泛型方法。在泛型型別和泛型方法中都會有型別參數,當通過泛型型別執行個體化對象或者對泛型方法調用的時候,都需要使用一個真實的類型來代替型別參數。
型別參數是真實類型的預留位置,在泛型聲明過程中,所有的型別參數放在一對間括弧中(<>),通過逗號分隔。
泛型型別
根據型別參數不同的指定類型實參的情況,泛型型別可以分為:
- 如果沒有為型別參數提供類型實參,那麼聲明的就是一個未綁定泛型型別(unbound generic)
- 如果指定了類型實參,該類型就稱為已構造類型(constructed type),然而已構造類型又可以是開放類型或封閉類型的
- 包含型別參數的類型就是開放類型(open type)(所有的未綁定的泛型型別都屬於開放類型的),
- 每個型別參數都指定了類型實參就是封閉類型(closed type)
類型是對象的藍圖,我們可以通過類型來執行個體化對象;那麼對於泛型來說,未綁定泛型型別是以建構的泛型型別的藍圖,已建構的泛型型別又是實際對象的藍圖。
就是一個簡單的例子,Dictionary<TKey, TValue>就是一個泛型型別(未綁定泛型型別,開放類型);通過制定型別參數,可以得到不同的封閉類型;通過不同的封閉類型有可以構造不同的執行個體。
泛型方法
我們都已經習慣了方法的參數和傳回值擁有固定的類型,這裡就看看“參數化”的方法。對於泛型方法,可以理解為擁有型別參數的方法。
對於上面例子中Dictionary<TKey, TValue>這個泛型型別,有很多方法可以使用,例如:
- void Add(TKey, key, TValue value)
- bool ContainsValue(TValue value)
- bool ContainsKey(TKey key)
注意,這些方法中沒有一個是真正的泛型方法,他們只是使用了泛型型別的型別參數。
真正的泛型方法應該擁有自己的型別參數,當我們使用泛型方法的時候,要給泛型方法的類新參數指定類型實參,接下來看一個泛型方法的例子。
class Program{ static void Main(string[] args) { Console.WriteLine("The bigger one is {0}", GetBiggerOne<int>(3,9)); Console.WriteLine("The bigger one is {0}", GetBiggerOne<string>("Hello", "World")); Console.Read(); } public static T GetBiggerOne<T>(T itemOne, T itemTwo) where T : IComparable { if (itemOne.CompareTo(itemTwo) > 0) { return itemOne; } return itemTwo; }}
在上面的例子中,我們使用泛型方法來實現一個兩個元素比較的例子,我們看到方法"GetBiggerOne"擁有自己的型別參數,當我們看到一個泛型方法時,可以一步步用真實的類型替換泛型方法中的型別參數,這樣就會簡化我們的分析。
對於泛型的類型約束,將在下面一篇文章介紹。
泛型的優點
根據上面的分析,可以看到泛型有一些的優點:
- 代碼重用
- 泛型提供的代碼的重用,確切的說應該是 "邏輯和演算法的重用"。從前面的泛型方法例子可以看到,通過泛型可以避免為每種特定的類型實現一個比較方法。
- 型別安全
- 泛型型別保證了型別安全,可以在編譯期就發現類型不符的問題,而不是等到運行時
- 效率
- 避免實值型別的裝箱和拆箱引起的效率問題(後面會簡單介紹為什麼泛型可以避免裝箱和拆箱)
總結
泛型的出現,給我們帶來了很多好處,泛型實現了類型和方法的"參數化"。
基於泛型,我們可以實現代碼重用,並且泛型為我們提供了型別安全檢查。對於實值型別的操作,通過泛型可以避免裝箱和拆箱帶來的效能損失。
同樣C# 2.0 以後,就建議只在代碼中使用支援泛型的集合類了(System.Collections.Generic)。
理解C#泛型