在討論泛型以前,咱們先討論一下在沒有泛型的世界裏,若是咱們想要建立一個獨立於被包含類型的類和方法,咱們須要定義objece類型,可是使用object就要面對裝箱和拆箱的操做,裝箱和拆箱會很損耗性能,咱們接下來會用一個示例來講明使用泛型和使用非泛型對值操做時的性能差距。可是若是使用泛型,也是一樣的效果,不須要裝箱和拆箱的同時泛型還保證了類型安全html
言歸正傳,.Net自2.0之後就開始支持泛型,CLR容許建立泛型引用類型和泛型值類型,但不容許建立泛型枚舉類型,此外,CLR還容許建立泛型接口和泛型委託。先來簡單看一下泛型的語法。算法
1、爲何要有泛型?數組
咱們在寫一些方法時可能會方法名相同,參數類型不一樣的方法,這種叫作重載。若是隻是由於參數類型不一樣裏面作的業務邏輯都是相同的,那可能就是複製粘貼方法,改變參數類型,例如一些排序算法,int、float、double等類型的排序,參數數組存的數據類型不同,還有像根據索引找到List集合中的對象。可能這個對象是Person、Dog等對象,這樣方法改變的只是參數類型,那就是能不能寫一個方法,傳遞不一樣的參數類型呢?因而有了泛型。安全
2、什麼是泛型?ide
泛型經過參數化類型來實如今同一份代碼上操做多種數據類型。例如使用泛型的類型參數T,定義一個類Stack<T>,能夠用Stack<int>、Stack<string>或Stack<Person>實例化它,從而使類Stack能夠處理int、string、Person類型數據。這樣能夠避免運行時類型轉換或封箱操做的代價和風險,相似C++的模板。泛型提醒的是將具體的東西模糊化,這與後面的反射正好相反。post
3、泛型的語法性能
需引用命名空間System.Collections.Generic,泛型List類後面添加了一個<T>,代表操做的是一個未指定的數據類型,在泛型聲明過程當中,全部的類型參數放在間括號中(<>),經過逗號分隔。優化
命名約定ui
public class List<T>{}
Public class SortedList<Tkey,Tvalue>{}
泛型和非泛型性能對比this
下面的示例說明,示例對比了使用泛型和不使用泛型的對比
1 static void Main(string[] args) 2 { 3 Stopwatch stopwatch = new Stopwatch(); 4 stopwatch.Start(); // 開始監視代碼運行時間 5 List<long> listint = new List<long>(); 6 Add(listint); 7 stopwatch.Stop(); // 中止監視 8 TimeSpan timespan = stopwatch.Elapsed; 9 double milliseconds = timespan.TotalMilliseconds; 10 Console.WriteLine("泛型用時"+milliseconds); 11 12 13 Stopwatch stopwatch1 = new Stopwatch(); 14 stopwatch1.Start(); // 開始監視代碼運行時間 15 ArrayList alistint = new ArrayList(); 16 Addf(alistint); 17 stopwatch1.Stop(); // 中止監視 18 TimeSpan timespan1 = stopwatch1.Elapsed; 19 double milliseconds1 = timespan1.TotalMilliseconds; 20 Console.WriteLine("非泛型用時"+milliseconds1); 21 22 } 23 24 public static void Add(List<long> a) 25 { 26 long sum = 0; 27 for (long i = 0; i < 1000000; i++) 28 { 29 a.Add(i); 30 } 31 foreach (var item in a) 32 {sum += item;} 33 Console.WriteLine("泛型集合結果"+sum); 34 } 35 36 37 public static void Addf(ArrayList a) { 38 long sum = 0; 39 for (long i = 0; i < 1000000; i++) 40 { 41 a.Add(i); 42 } 43 foreach (var item in a) 44 { sum+=Convert.ToInt32(item); } 45 Console.WriteLine("非泛型集合結果"+sum); 46 47 }
運行結果以下,在一百萬次值類型循環下泛型要比非泛型省下50毫秒左右的時間,固然這點時間看起來不多,可是程序中每每不僅是示例這樣簡單的邏輯,每每包含很對更加複雜的邏輯,時間和性能上的差距就會變得很大。
類型安全
從下面的例子能夠看出,非泛型集合能夠賦值任何類型,取出時只須要拆箱操做,泛型集合則須要賦值指定類型值,不然就會報錯,從類型的安全角度來看,泛型的類型都是符合要求的類型才能放進來,因此更安全,而非泛型的集合則須要辨別其中那些是符合類型的,那些是不須要的
1 List<int> list = new List<int>();//建立泛型集合 2 ArrayList arry = new ArrayList();//非泛型集合 3 list.Add(1);//能夠 4 list.Add("zj");//報錯 5 6 arry.Add(1);//能夠 7 arry.Add("張三");//能夠
泛型類型
根據類型參數不一樣的指定類型實參的狀況,泛型類型能夠分爲:
類型是對象的藍圖,咱們能夠經過類型來實例化對象;那麼對於泛型來講,未綁定泛型類型是以構造泛型類型的藍圖,已構造泛型類型又是實際對象的藍圖。
下圖就是一個簡單的例子,Dictionary<TKey, TValue>就是一個泛型類型(未綁定泛型類型,開放類型);經過制定類型參數,能夠獲得不一樣的封閉類型;經過不一樣的封閉類型有能夠構造不一樣的實例。
泛型類型和繼承
泛型類型仍然是類型,因此能從其餘任何類型派生,使用泛型類型並指定類型實參時,實際上在CLR中定義新的類型對象,新的類型對象從泛型類型派生自的那個類型派生,
換句話說,List<T>是從object派生,因此List<string>,List<int>也都派生自object,相似地,因爲DictionaryStringKey<TValue>派生自Dictionary<String,TValue>因此DictionaryStringKey<Guid>派生自Dictionary<String,Guid>,類型實參的指定和繼承層次結構沒有任何關係--理解這一點,有助於判斷哪些轉型是可以進行的,哪些轉型是不能進行的。
接下來看代碼示例:
1 internal class Node{ 2 protected Node m_next; 3 public Node(Node next) { 4 m_next = next; 5 } 6 } 7 internal sealed class TypeNode<T> : Node { 8 public T m_data; 9 10 public TypeNode(T data) : this(data, null) { } 11 12 public TypeNode(T data, Node next) 13 : base(next) 14 { 15 m_data = data; 16 } 17 public override string ToString() 18 { 19 return m_data.ToString() + 20 ((m_next != null) ? m_next.ToString() : null); 21 } 22 } 23 24 程序入口: 25 Node head = new TypeNode<Char>('.'); 26 head = new TypeNode<DateTime>(DateTime.Now, head); 27 head = new TypeNode<String>("Today is ", head); 28 Console.WriteLine(head.ToString()); 29 程序輸出: 30 Today is 2012-11-23 16:33:14.
定義一個非泛型的Node基類,在定義一個泛型TypedNode類(用Node類做爲基類),這樣依賴就能夠建立一個鏈表,每一個節點均可以是一種具體的數據類型(非Object類型),同時防止裝箱,有點遞歸的意思。
代碼爆炸
使用泛型類型參數的一個方法在進行JIT編譯時,CLR獲取方法的IL,用指定的類型實參進行替換,而後建立恰當的本地代碼。然而,這樣作有一個缺點:CLR要爲每種不一樣的方法/類型組合生成本地代碼。咱們將這個現象稱爲"代碼爆炸"。它可能形成引用程序集的顯著增大,從而影響性能。
CLR內建了一些優化措施,能緩解代碼爆炸。首先,假如爲一個特定的類型實參調用了一個方法,之後再次使用相同的類型實參來調用這個方法,CLR只會爲這個方法/類型組合編譯一次。因此,若是一個程序集使用List<DateTime>,一個徹底不一樣的程序集也使用List<DateTime>,CLR只會爲List<DateTime>編譯一次方法。
CLR還提供了一個優化措施,它認爲全部引用類型實參都是徹底相同的,因此代碼可以共享。之因此能這樣,是由於全部引用類型的實參或變量時間只是執行堆上的對象的指針,而對象指針所有是以相同的方式操做的。
可是,假如某個類型實參是值類型,CLR就必須專門爲那個值類型生成本地代碼。由於值類型的大小不定。即便類型、大小相同,CLR仍然沒法共享代碼,可能須要用不一樣的本地CPU指令操做這些值。
結束語:
泛型的知識比預想的要多不少,一篇文章所有寫完很長,會分爲兩個部分,將在二中繼續研究泛型接口和泛型委託,協變和逆變泛型類型參數,泛型方法,和約束性
引用:http://www.cnblogs.com/wilber2013/p/4291435.html#_nav_0 田小計劃 理解C#泛型
http://www.cnblogs.com/Ming8006/p/3789847.html#c.d 明-Ming 《CLR via C#》讀書筆記 之 泛型
http://www.cnblogs.com/liuhailiang/archive/2012/11/26/2788642.html Lordbaby 泛型(三)泛型類型和繼承