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

字號(hào):

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