C#2.0中泛型編程初級(jí)入門(mén)教程

字號(hào):

在2005年底微軟公司正式發(fā)布了C# 2.0,與C# 1.x相比,新版本增加了很多新特性,其中最重要的是對(duì)泛型的支持。通過(guò)泛型,我們可以定義類(lèi)型安全的數(shù)據(jù)結(jié)構(gòu),而無(wú)需使用實(shí)際的數(shù)據(jù)類(lèi)型。這能顯著提高性能并得到更高質(zhì)量的代碼。泛型并不是什么新鮮的東西,他在功能上類(lèi)似于C++的模板,模板多年前就已存在C++上了,并且在C++上有大量成熟應(yīng)用。
    本文討論泛型使用的一般問(wèn)題,比如為什么要使用泛型、泛型的編寫(xiě)方法、泛型中數(shù)據(jù)類(lèi)型的約束、泛型中靜態(tài)成員使用要注意的問(wèn)題、泛型中方法重載的問(wèn)、泛型方法等,通過(guò)這些使我們可以大致了解泛型并掌握泛型的一般應(yīng)用,編寫(xiě)出更簡(jiǎn)單、通用、高效的應(yīng)用系統(tǒng)。
    什么是泛型
    我們?cè)诰帉?xiě)程序時(shí),經(jīng)常遇到兩個(gè)模塊的功能非常相似,只是一個(gè)是處理int數(shù)據(jù),另一個(gè)是處理string數(shù)據(jù),或者其他自定義的數(shù)據(jù)類(lèi)型,但我們沒(méi)有辦法,只能分別寫(xiě)多個(gè)方法處理每個(gè)數(shù)據(jù)類(lèi)型,因?yàn)榉椒ǖ膮?shù)類(lèi)型不同。有沒(méi)有一種辦法,在方法中傳入通用的數(shù)據(jù)類(lèi)型,這樣不就可以合并代碼了嗎?泛型的出現(xiàn)就是專(zhuān)門(mén)解決這個(gè)問(wèn)題的。讀完本篇文章,你會(huì)對(duì)泛型有更深的了解。
    為什么要使用泛型
    為了了解這個(gè)問(wèn)題,我們先看下面的代碼,代碼省略了一些內(nèi)容,但功能是實(shí)現(xiàn)一個(gè)棧,這個(gè)棧只能處理int數(shù)據(jù)類(lèi)型:
    public class Stack
    {
    private int[] m_item;
    public int Pop(){...}
    public void Push(int item){...}
    public Stack(int i)
    {
    this.m_item = new int[i];
    }
    }
    上面代碼運(yùn)行的很好,但是,當(dāng)我們需要一個(gè)棧來(lái)保存string類(lèi)型時(shí),該怎么辦呢?很多人都會(huì)想到把上面的代碼復(fù)制一份,把int改成string不就行了。當(dāng)然,這樣做本身是沒(méi)有任何問(wèn)題的,但一個(gè)優(yōu)秀的程序是不會(huì)這樣做的,因?yàn)樗氲饺粢院笤傩枰猯ong、Node類(lèi)型的棧該怎樣做呢?還要再?gòu)?fù)制嗎??jī)?yōu)秀的程序員會(huì)想到用一個(gè)通用的數(shù)據(jù)類(lèi)型object來(lái)實(shí)現(xiàn)這個(gè)棧:
    public class Stack
    {
    private object[] m_item;
    public object Pop(){...}
    public void Push(object item){...}
    public Stack(int i)
    {
    this.m_item = new[i];
    }
    }
    這個(gè)棧寫(xiě)的不錯(cuò),他非常靈活,可以接收任何數(shù)據(jù)類(lèi)型,可以說(shuō)是一勞永逸。但全面地講,也不是沒(méi)有缺陷的,主要表現(xiàn)在:
    當(dāng)Stack處理值類(lèi)型時(shí),會(huì)出現(xiàn)裝箱、折箱操作,這將在托管堆上分配和回收大量的變量,若數(shù)據(jù)量大,則性能損失非常嚴(yán)重。
    在處理引用類(lèi)型時(shí),雖然沒(méi)有裝箱和折箱操作,但將用到數(shù)據(jù)類(lèi)型的強(qiáng)制轉(zhuǎn)換操作,增加處理器的負(fù)擔(dān)。
    在數(shù)據(jù)類(lèi)型的強(qiáng)制轉(zhuǎn)換上還有更嚴(yán)重的問(wèn)題(假設(shè)stack是Stack的一個(gè)實(shí)例):
    Node1 x = new Node1();
    stack.Push(x);
    Node2 y = (Node2)stack.Pop();
    上面的代碼在編譯時(shí)是完全沒(méi)問(wèn)題的,但由于Push了一個(gè)Node1類(lèi)型的數(shù)據(jù),但在Pop時(shí)卻要求轉(zhuǎn)換為Node2類(lèi)型,這將出現(xiàn)程序運(yùn)行時(shí)的類(lèi)型轉(zhuǎn)換異常,但卻逃離了編譯器的檢查。
    針對(duì)object類(lèi)型棧的問(wèn)題,我們引入泛型,他可以?xún)?yōu)雅地解決這些問(wèn)題。泛型用用一個(gè)通過(guò)的數(shù)據(jù)類(lèi)型T來(lái)代替object,在類(lèi)實(shí)例化時(shí)指定T的類(lèi)型,運(yùn)行時(shí)(Runtime)自動(dòng)編譯為本地代碼,運(yùn)行效率和代碼質(zhì)量都有很大提高,并且保證數(shù)據(jù)類(lèi)型安全。
    使用泛型
    下面是用泛型來(lái)重寫(xiě)上面的棧,用一個(gè)通用的數(shù)據(jù)類(lèi)型T來(lái)作為一個(gè)占位符,等待在實(shí)例化時(shí)用一個(gè)實(shí)際的類(lèi)型來(lái)代替。讓我們來(lái)看看泛型的威力:
    public class Stack
    {
    private T[] m_item;
    public T Pop(){...}
    public void Push(T item){...}
    public Stack(int i)
    {
    this.m_item = new T[i];
    }
    }
    類(lèi)的寫(xiě)法不變,只是引入了通用數(shù)據(jù)類(lèi)型T就可以適用于任何數(shù)據(jù)類(lèi)型,并且類(lèi)型安全的。這個(gè)類(lèi)的調(diào)用方法:
    //實(shí)例化只能保存int類(lèi)型的類(lèi)
    Stack a = new Stack(100);
    a.Push(10);
    a.Push("8888"); //這一行編譯不通過(guò),因?yàn)轭?lèi)a只接收int類(lèi)型的數(shù)據(jù)
    int x = a.Pop();
    //實(shí)例化只能保存string類(lèi)型的類(lèi)
    Stack b = new Stack(100);
    b.Push(10); //這一行編譯不通過(guò),因?yàn)轭?lèi)b只接收string類(lèi)型的數(shù)據(jù)
    b.Push("8888");
    string y = b.Pop();
    這個(gè)類(lèi)和object實(shí)現(xiàn)的類(lèi)有截然不同的區(qū)別:
    1. 他是類(lèi)型安全的。實(shí)例化了int類(lèi)型的棧,就不能處理string類(lèi)型的數(shù)據(jù),其他數(shù)據(jù)類(lèi)型也一樣。
    2. 無(wú)需裝箱和折箱。這個(gè)類(lèi)在實(shí)例化時(shí),按照所傳入的數(shù)據(jù)類(lèi)型生成本地代碼,本地代碼數(shù)據(jù)類(lèi)型已確定,所以無(wú)需裝箱和折箱。
    3. 無(wú)需類(lèi)型轉(zhuǎn)換。