深入淺出NET泛型編程NET泛型編程_第1頁
深入淺出NET泛型編程NET泛型編程_第2頁
深入淺出NET泛型編程NET泛型編程_第3頁
深入淺出NET泛型編程NET泛型編程_第4頁
深入淺出NET泛型編程NET泛型編程_第5頁
已閱讀5頁,還剩13頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

前言.NET2.0中泛型的出現(xiàn)是一個令人激動的特征。但是,什么是泛型?你需要它們嗎?你會在自己的應(yīng)用軟件中使用它們?在本文中,我們將回答這些問題并細致地分析泛型的使用,能力及其局限性。類型安全?NET中的許多語言如C#,C++和VB.NET(選項strict為on)都是強類型語言。作為一個程序員,當你使用這些語言時,總會期望編譯器進行類型安全的檢查。例如,如果你把對一個Book類型的引用轉(zhuǎn)換成一個Vehicle型的引用,編譯器將告訴你這樣的cast是無效的。然而,當談到.NET1.0和1.1中的集合時,它們是無助于類型安全的。請考慮一個ArrayList的例子,它擁有一個對象集合一這允許你把任何類型的對象放于該ArrayList中。讓我們看一下例1中的代碼。例1.缺乏類型安全的ArrayListusingSystem;usingSystem.Collections;namespaceTestApp{classTest{[STAThread]staticvoidMain(string[]args){ArrayListlist=newArrayList();list.Add(3);list.Add(4);//list.Add(5.0);inttotal=0;foreach(intvalinlist){total=total+val;Console.WriteLine("Totalis{0}",total);}}}本例中,我們建立了一個ArrayList的實例,并把3和4添加給它。然后我循環(huán)遍歷該ArrayList,從中取出整型值然后把它們相加。這個程序?qū)a(chǎn)生結(jié)果"Totalis7"?,F(xiàn)在,如果我注釋掉下面這句:list.Add(5.0);程序?qū)a(chǎn)生如下的運行時刻異常:UnhandledException:System.InvalidCastException:Specifiedcastisnotvalid.AtTestApp.Test.Main(String[]args)in:\workarea\testapp\class1.cs:line17哪里出錯了呢?記住ArrayList擁有一個集合的對象。當你把3加到ArrayList上時,你已把值3裝箱了。當你循環(huán)該列表時,你是把元素拆箱成int型。然而,當你添加值5.0時,你在裝箱一個double型值。在第17行,那個double值被拆箱成一個int型。這就是失敗的原因。注意:上面的實例,如果是用VB.NET書寫的話,是不會失敗的。原因在于,VB.NET不使用裝箱機制,它激活一個把該double轉(zhuǎn)換成整型的方法。但是,如果ArrayList中的值是不能轉(zhuǎn)換成整型的,VB.NET代碼還會失敗。作為一個習(xí)慣于使用語言提供的類型安全的程序員,你希望這樣的問題在編譯期間浮出水面,而不是在運行時刻。這正是泛型產(chǎn)生的原因。什么是泛型?泛型允許你在編譯時間實現(xiàn)類型安全。它們允許你創(chuàng)建一個數(shù)據(jù)結(jié)構(gòu)而不限于一特定的數(shù)據(jù)類型。然而,當使用該數(shù)據(jù)結(jié)構(gòu)時,編譯器保證它使用的類型與類型安全是相一致的泛型提供了類型安全,但是沒有造成任何性能損失和代碼臃腫。在這方面,它們很類似于C++中的模板,不過它們在實現(xiàn)上是很不同的。使用泛型集合.NET2.0的System.Collections.Generics命名空間包含了泛型集合定義。各種不同的集合/容器類都被"參數(shù)化"了。為使用它們,只需簡單地指定參數(shù)化的類型即可。請看例2:例2.類型安全的泛型列表ListVint>aList二newListVint>();aList.Add(3);aList.Add(4);//aList.Add(5.0);inttotal=0;foreach(intvalinaList){total=total+val;}Console.WriteLine("Totalis{0}",total);在例2中,我編寫了一個泛型的列表的例子,在尖括號內(nèi)指定參數(shù)類型為int。該代碼的執(zhí)行將產(chǎn)生結(jié)果"Totalis7"。現(xiàn)在,如果我去掉語句doubleList.Add(5.0)的注釋,我將得到一個編譯錯誤。編譯器指出它不能發(fā)送值5.0到方法Add(),因為該方法僅接受int型。不同于例1,這里的代碼實現(xiàn)了類型安全。CLR對于泛型的支持泛型不僅是一個語言級上的特征。.NETCLR能識別出泛型。在這種意義上說,泛型的使用是.NET中最為優(yōu)秀的特征之一。對每個用于泛型化的類型的參數(shù),類也同樣沒有脫離開微軟中間語言(MSIL)。換句話說,你的配件集僅包含你的參數(shù)化的數(shù)據(jù)結(jié)構(gòu)或類的一個定義,而不管使用多少種不同的類型來表達該參數(shù)化的類型。例如,如果你定義一個泛型類型MyListVT>,僅僅該類型的一個定義出現(xiàn)在MSIL中。當程序執(zhí)行時,不同的類被動態(tài)地創(chuàng)建,每個類對應(yīng)該參數(shù)化類型的一種類型。如果你使用MyListVint>和MyListVdouble>,有兩種類即被創(chuàng)建。當你的程序執(zhí)行時,讓我們進一步在例3中分析這一點。例3.創(chuàng)建一個泛型類//MyList.cs#regionUsingdirectivesusingSystem;usingSystem.Collections.Generic;usingSystem.Text;#endregionnamespaceCLRSupportExample{publicclassMyListVT>{privatestaticintobjCount=0;publicMyList(){objCount++;}publicintCount{get{returnobjCount;}}}}//Program.cs#regionUsingdirectivesusingSystem;usingSystem.Collections.Generic;usingSystem.Text;#endregionnamespaceCLRSupportExample{classSampleClass{}classProgram{staticvoidMain(string[]args){MyListVint>mylntList二newMyListVint>();MyListVint>mylntList2=newMyListVint>();MyListVdouble>myDoubleList二newMyListVdouble>();MyListVSampleClass>mySampleList二newMyListVSampleClass>();Console.WriteLine(myIntList.Count);Console.WriteLine(myIntList2.Count);Console.WriteLine(myDoubleList.Count);Console.WriteLine(mySampleList.Count);Console.WriteLine(newMyListVsampleclass>().Count);Console.ReadLine();}}}該例中,我創(chuàng)建了一個稱為MyList泛型類。為把它參數(shù)化,我簡單地插入了一個尖括號。在<>內(nèi)的T代表了實際的當使用該類時要指定的類型。在MyList類中,定義了一個靜態(tài)字段objCount。我在構(gòu)造器中增加它的值。因此我能發(fā)現(xiàn)使用我的類的用戶共創(chuàng)建了多少個那種類型的對象。屬性Count返回與被調(diào)用的實例同類型的實例的數(shù)目。在Main()方法,我創(chuàng)建了MyListVint>的兩個實例,一個MyListVdouble>的實例,還有兩個MyListVSampleClass>的實例一其中SampleClass是我已定義了的類。問題是:Count(上面的程序的輸出)的值該是多少?在你繼閱讀之前,試一試回答這個問題。解決了上面的問題?你得到下列的答案了嗎?22112前面兩個2對應(yīng)MyListVint>,第一個1對應(yīng)MyListVdouble>,第二個1對應(yīng)MyListVSampleClass>—在此,僅創(chuàng)建一個這種類型的實例。最后一個2對應(yīng)MyListVSampleClass〉,因為代碼中又創(chuàng)建了這種類型的另外一個實例。上面的例子說明MyListVint>是一個與MyListVdouble>不同的類,而MyListVdouble〉又是一個與MyListVSampleClass>不同的類。因此,在這個例中,我們有四個類:MyList:MyListVT>,MyListVint>,MyListVdouble>和MyListVX>。注意,雖然有4個MyList類,但僅有一個被存儲在MSIL。怎么能證明這一點?請看圖1顯示出的使用工具ildasm.exe生成的MSIL代碼。泛型方法除了有泛型類,你也可以有泛型方法。泛型方法可以是任何類的一部分。讓我們看一下例4:例4.一個泛型方法publicclassProgram{publicstaticvoidCopyVT>(ListVT>source,ListVT>destination){foreach(Tobjinsource){destination.Add(obj);}}staticvoidMain(string[]args){ListVint>lstl二newListVint>();lst1.Add(2);lst1.Add(4);ListVint>lst2=newListVint>();Copy(lst1,lst2);Console.WriteLine(lst2.Count);}}Copy()方法就是一個泛型方法,它與參數(shù)化的類型T一起工作。當在Main()中激活Copy()時,編譯器根據(jù)提供給Copy()方法的參數(shù)確定出要使用的具體類型。無限制的類型參數(shù)如果你創(chuàng)建一個泛型數(shù)據(jù)結(jié)構(gòu)或類,就象例3中的MyList,注意其中并沒有約束你該使用什么類型來建立參數(shù)化類型。然而,這帶來一些限制。如,你不能在參數(shù)化類型的實例中使用象==,!=或<等運算符,如:象==和!=這樣的運算符的實現(xiàn)對于值類型和引用類型都是不同的。如果隨意地允許之,代碼的行為可能很出乎你的意料。另外一種限制是缺省構(gòu)造器的使用。例如,如果你編碼象newT(),會出現(xiàn)一個編譯錯,因為并非所有的類都有一個無參數(shù)的構(gòu)造器。如果你真正編碼象newT()來創(chuàng)建一個對象,或者使用象==和!=這樣的運算符,情況會是怎樣呢?你可以這樣做,但首先要限制可被用于參數(shù)化類型的類型。讀者可以自己先考慮如何實現(xiàn)之。約束機制及其優(yōu)點一個泛型類允許你寫自己的類而不必拘泥于任何類型,但允許你的類的使用者以后可以指定要使用的具體類型。通過對可能會用于參數(shù)化的類型的類型施加約束,這給你的編程帶來很大的靈活性--你可以控制建立你自己的類。讓我們分析一個例子:例5.需要約束:代碼不會編譯成功例5中的代碼將產(chǎn)生一個編譯錯誤:Error1'T'doesnotcontainadefinitionfor'CompareTo'假定我需要這種類型以支持CompareTo()方法的實現(xiàn)。我能夠通過加以約束一為參數(shù)化類型指定的類型必須要實現(xiàn)【Comparable接口一來指定這一點。例6中的代碼就是這樣:例6.指定一個約束publicstaticTMax<T>(Top1,Top2)whereT:IComparable{在例6中,我指定的約束是,用于參數(shù)化類型的類型必須繼承自(實現(xiàn)"comparable。下面的約束是可以使用的:whereT:struct類型必須是一種值類型(struct)whereT:class類型必須是一種引用類型(class)whereT:new()類型必須有一個無參數(shù)的構(gòu)造器whereT:class_name類型可以是class_name或者是它的一個子類whereT:interface_name類型必須實現(xiàn)指定的接口你可以指定約束的組合,就象:whereT:IComparable,new(。)這就是說,用于參數(shù)化類型的類型必須實現(xiàn)【comparable接口并且必須有一個無參構(gòu)造器。繼承與泛型一個使用參數(shù)化類型的泛型類,象MyClasslVT>,稱作開放結(jié)構(gòu)的泛型。一個不使用參數(shù)化類型的泛型類,象MyClasslVint>,稱作封閉結(jié)構(gòu)的泛型。你可以從一個封閉結(jié)構(gòu)的泛型進行派生;也就是說,你可以從另外一個稱為MyClassl的類派生一個稱為MyClass2的類,就象:publicclassMyClass2<T>:MyClass1<int>你也可以從一個開放結(jié)構(gòu)的泛型進行派生,如果類型被參數(shù)化的話,如:publicclassMyClass2<T>:MyClass2<T>是有效的,但是publicclassMyClass2<T>:MyClass2<Y>是無效的,這里Y是一個被參數(shù)化的類型。非泛型類可以從一個封閉結(jié)構(gòu)的泛型類進行派生,但是不能從一個開放結(jié)構(gòu)的泛型類派生。即:publicclassMyClass:MyClassl<int>是有效的,但是publicclassMyClass:MyClassl<T>是無效的。泛型和可代替性當我們使用泛型時,要小心可代替性的情況。如果B繼承自A,那么在使用對象A的地方,可能都會用到對象B。假定我們有一籃子水果(aBasketofFruits(BasketVFruit>)),而且有繼承自Fruit的Apple和Banana(皆為Fruit的種類)。一籃子蘋果一BasketofApples(BasketVapple>)可以繼承自BasketofFruits(BasketVFruit>)?答案是否定的,女口果我們考慮一下可代替性的話。為什么?請考慮一個aBasketofFruits可以工作的方法:如果發(fā)送一個BasketVFruit>的實例給這個方法,這個方法將添加一個Apple對象和一個Banana對象。然而,發(fā)送一個BasketVApple>的實例給這個方法時,會是什么情形呢?你看,這里充滿技巧。這解釋了為什么下列代碼:BasketVApple〉anAppleBasket=newBasketVApple〉();Package(anAppleBasket);會產(chǎn)生錯誤:Error2Argument'1':cannotconvertfrom'TestApp.BasketVtestapp.apple〉'to'TestApp.BasketVtestapp.fruit〉'編譯器通過確保我們不會隨意地傳遞一個集合的派生類(此時需要一個集合的基類),保護了我們的代碼。這不是很好嗎?這在上面的例中在成功的,但也存在特殊情形:有時我們確實想傳遞一個集合的派生類此時需要一個集合的基類。例如,考慮一下Animal(如Monkey),它有一個把BasketVFruit〉作參數(shù)的方法Eat,如下所示:現(xiàn)在,你可以調(diào)用:BasketVFruit>fruitsBasket=newBasketVFruit>();…//添加到Basket對象中的對象FruitanAnimal.Eat(fruitsBasket);如果你有一籃子(aBasketof)Banana-—BasketVBanana>,情況會是如何呢?把一籃子(aBasketof)Banana-—BasketVBanana>發(fā)送給Eat方法有意義嗎?在這種情形下,會成功嗎?真是這樣的話,編譯器會給出錯誤信息:BasketVBanana>bananaBasket=newBasketVBanana>();//...anAnimal?Eat(bananaBasket);編譯器在此保護了我們的代碼。我們怎樣才能要求編譯器允許這種特殊情形呢?約束機制再一次幫助了我們:在建立方法Eat()的過程中,我要求編譯器允許一籃子(aBasketof)任何類型T,這里T是Fruit類型或任何繼承自Fruit的類。11?泛型和代理代理也可以是泛型化的。這樣就帶來了巨大的靈活性。假定我們對寫一個框架程序很感興趣。我們需要提供一種機制給事件源以使之可以與對該事件感興趣的對象進行通訊。我們的框架可能無法控制事件是什么。你可能在處理某種股票價格變化(doubleprice),而我可能在處理水壺中的溫度變化(temperaturevalue),這里Temperature可以是一種具有值、單位、門檻值等信息的對象。那么,怎樣為這些事件定義一接口呢?讓我們通過pre-generic代理技術(shù)細致地分析一下如何實現(xiàn)這些:publicdelegatevoidNotifyDelegate(Objectinfo);publicinterfaceISource{eventNotifyDelegateNotifyActivity;}我們讓NotifyDelegate接受一個對象。這是我們過去采取的最好措施,因為Object可以用來代表不同類型,如double,Temperature,等等一盡管Object含有因值類型而產(chǎn)生的裝箱的開銷。ISource是一個各種不同的源都會支持的接口。這里的框架展露了NotifyDelegate代理和ISource接口。讓我們看兩個不同的源碼:如果我們各有一個上面每個類的對象,我們將為事件注冊一個處理器,如下所示:StockPriceSourcestockSource=newStockPriceSource();stockSource.NotifyActivity+=newNotifyDelegate(stockSource_NotifyActivity);//這里不必要出現(xiàn)在同一個程序中BoilerSourceboilerSource=newBoilerSource();boilerSource.NotifyActivity+=newNotifyDelegate(boilerSource_NotifyActivity);在代理處理器方法中,我們要做下面一些事情:對于股票事件處理器,我們有:voidstockSource_NotifyActivity(objectinfo){doubleprice=(double)info;//在使用前downcast需要的類型}溫度事件的處理器看上去會是:voidboilerSource_NotifyActivity(objectinfo){Temperaturevalue=infoasTemperature;//在使用前downcast需要的類型上面的代碼并不直觀,且因使用downcast而有些凌亂。借助于泛型,代碼將變得更易讀且更容易使用。讓我們看一下泛型的工作原理:下面是代理和接口:publicdelegatevoidNotifyDelegate<t>(Tinfo);publicinterfaceISource<t>{eventNotifyDelegate<t>NotifyActivity;}我們已經(jīng)參數(shù)化了代理和接口?,F(xiàn)在的接口的實現(xiàn)中應(yīng)該能確定這是一種什么類型。Stock的源代碼看上去象這樣:而Boiler的源代碼看上去象這樣:publicclassBoilerSource:ISource<temperature>{publiceventNotifyDelegate<temperature>NotifyActivity;//...}如果我們各有一個上面每種類的對象,我們將象下面這樣來為事件注冊一處理器:StockPriceSourcestockSource=newStockPriceSource();stockSource?NotifyActivity+=newNotifyDelegate<double>(stockSource_NotifyActivity);//這里不必要出現(xiàn)在同一個程序中BoilerSourceboilerSource=newBoilerSource();boilerSource.NotifyActivity+=newNotifyDelegateVtemperature〉(boilerSource_NotifyActivity);現(xiàn)在,股票價格的事件處理器會是:voidstockSource_NotifyActivity(doubleinfo){//…}溫度的事件處理器是:voidboilerSource_NotifyActivity(Temperatureinfo){//…}這里的代碼沒有作downcast并且使用的類型是很清楚的。泛型與反射既然泛型是在CLR級上得到支持的,你可以使用反射API來取得關(guān)于泛型的信息。如果你是編程的新手,可能有一件事讓你疑惑:你必須記住既有你寫的泛型類也有在運行時從該泛型類創(chuàng)建的類型。因此,當使用反射API時,你需要另外記住你在使用哪一種類型。我將在例7說明這一點:例7.在泛型上的反射Console.WriteLine("objl'sType");Console.WriteLine(typel.FullName);Console.WriteLine(typel.GetGenericTypeDefinition().FullName);Console.WriteLine("obj2'sType");Console.WriteLine(type2.FullName);Console.WriteLine(type2.GetGenericTypeDefinition().FullName);}}在本例中,有一個MyClassVint>的實例,程序中要查詢該實例的類名。然后我查詢這種類型的GenericTypeDefinition()。GenericTypeDefinition()會返回MyClassVT>的類型元數(shù)據(jù)。你可以調(diào)用IsGenericTypeDefinition來查詢是否這是一個泛型類型(象MyClassVT>)或者是否已指定它的類型參數(shù)(象MyClassVint>)。同樣地,我查詢MyClassVdouble>的實例的元數(shù)據(jù)。上面的程序輸出如下:objl'sTypeTestApp.MyClass'l[[System.Int32,mscorlib,Version=,Culture=neutral,PublicKeyToken=b77a5c561934e089]]TestApp.MyClass'lobj2'sTypeTestApp.MyClass'l[[System.Double,mscorlib,Version=,Culture=neutral,PublicKeyToken=b77a5c561934e089]]TestApp.MyClass'l可以看到,MyClassVin七>和MyClassVdouble>是屬于mscorlib配件集的類(動態(tài)創(chuàng)建的),而類MyClassVt>屬于我自建的配件集。泛型的局限性至此,我們已了解了泛型的強大威力。是否其也有不足呢?我發(fā)現(xiàn)了一處。我希望微軟能夠明確指出泛型存在的這一局制性。在表達約束的時候,我們能指定參數(shù)類型必須繼承自一個類。然而,指定參數(shù)必須是某種類的基類型該如何呢?為什么要那樣做呢?在例4中,我展示了一個Copy()方法,它能夠把一個源List的內(nèi)容復(fù)制到一個目標list中去。我可以象如下方式使用它:List<Apple>appleListl=newList<Apple>();List<Apple>appleList2=newList<Apple>();Copy(appleListl,appleList2);然而,如果我想要把apple對象從一個列表復(fù)制到另一個Fruit列表(Apple繼承自Fruit),情況會如何呢?當然,一個Fruit列表可以容納Apple對象。所以我要這樣編寫代碼:這不會成功編譯。你將得到一個錯誤:Error1Thetypeargumentsformethod'TestApp.Program?Copy<t>(System.Collections.Generic?List<t>,System.Collections.G

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論