国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發(fā) > Java > 正文

C#、Java以及C++的泛型

2024-07-21 02:26:57
字體:
供稿:網(wǎng)友

摘要

anders hejlsberg,c#的主架構(gòu)師,與bruce eckel和bill venners 談論了c#和java的泛型、c++模板、c#的constraints特性以及弱類型化和強類型化的問題。

anders hejlsberg,微軟的一位杰出工程師,他領導了c#(發(fā)音是c sharp)編程語言的設計團隊。hejlsberg首次躍上軟件業(yè)界舞臺是源于他在80年代早期為ms-dos和cp/m寫的一個pascal編譯器。不久一個叫做borland的非常年輕的公司雇傭了他并且買下了他的編譯器,從那以后這個編譯器就作為turbo pascal在市場上推廣。在borland,hejlsberg繼續(xù)開發(fā)turbo pacal并且在后來領導一個團隊設計turbo pascal的替代品:delphi。1996年,在borland工作13年以后,hejlsberg加入了微軟,在那里一開始作為visual j++和windows基礎類庫(wfc)的架構(gòu)師。隨后,hejlsberg擔任了c#的主要設計者和.net框架創(chuàng)建過程中的一個主要參與者。現(xiàn)在,anders hejlsberg領導c#編程語言的后續(xù)開發(fā)。

2003年7月30號,bruce eckel(《thinking in c++》以及《thinking in java》的作者)和bill venners(artima.com的主編)與anders hejlsberg在他位于華盛頓州redmond的微軟辦公室進行了一次面談。這次訪談的內(nèi)容將分多次發(fā)布在artima.com以及bruce eckel將于今年秋天發(fā)布的一張音頻光碟上。在這次訪談中,anders hejlsberg談論了c#語言和.net框架設計上的一些取舍。

·        在 第一部分:c#的設計過程中, hejlsberg談論了c#設計團隊所采用的流程,以及在語言設計中可用性研究(usability studies)和好的品味(good taste)相對而言的優(yōu)點。

·        在第二部分:checked exceptions的問題中, hejlsberg談論了已檢測異常(checked exceptions)的版本(versionability)問題和規(guī)模擴展(scalability)問題。

·        在第三部分: 委托、組件以及表面上的簡單性里,hejlsberg 談論了委托(delegates)以及c#對于組件的概念給予的頭等待遇。

·        在第四部分:版本,虛函數(shù)和覆寫里,hejlsberg解釋了談論了為什么c#的方法默認是非虛函數(shù),以及為什么程序員必須顯式指定覆寫(override)。

在第五部分:契約和互操作性里,hejlsberg談論了dll hell、接口契約、strong anmes以及互操作的重要性。
在第六部分:inappropriate abstractions里, hejlsberg以及c#團隊的其他成員談論了試圖讓網(wǎng)絡透明的分布式系統(tǒng),以及試圖屏蔽掉數(shù)據(jù)庫的對象——關系映射。  
在第七部分, hejlsberg比較了c#和java的泛型以及c++模板的實現(xiàn)方法,并且介紹了c#的constraints特性以及弱類型化和強類型化的問題。
泛型概述
bruce eckel: 能否就泛型做一個簡短的介紹?

anders hejlsberg: 泛型的本質(zhì)就是讓你的類型能夠擁有類型參數(shù)。它們也被成為參數(shù)化類型(parameterized types)或者參數(shù)的多態(tài)(parametric polymorphism)。經(jīng)典的例子十九一個list集合類。list是一個方便易用的、可增長的數(shù)組。它有一個排序方法,你可以通過索引來引用它的元素,等等。現(xiàn)今,如果沒有參數(shù)化類型,在使用數(shù)組或者lists之間就會有些別扭的地方。如果使用數(shù)組,你得到了強類型保證,因為你可以定義一個關于customer的數(shù)組,但是你沒有可增長性和那些方便易用的方法。如果你用的是list,雖然你得到了所有這些方便,但是卻喪失了強類型保證。你不能指定一個list是關于什么的list。它只是一個關于object的list。這會給你帶來一些問題。類型檢測必須在運行時刻做,也就意味著沒有在編譯時刻對類型進行檢測。即便是你塞給list一個customer對象然后試圖取出一個string,編譯器也不會有絲毫的抱怨。直到運行時刻你才會發(fā)現(xiàn)他會出問題。另外,當把基元類型(primitive type)放入list的時候,還必須對它們進行裝箱(box)。基于上述所有這些問題,lists與arrays之間的這種不和諧的地方總是存在的。到底選擇哪個,會讓你一直猶豫不決。

泛型的最大好處就是它讓你有了一個兩全其美的辦法(you can have your cake and eat it too),因為你可以定義一個list<t>[讀作:list of t]。當使用一個list的時候,你可以實實在在地知道這個list是關于什么類型的list,并且讓編譯器為你做強類型檢測。這只是它最直接的好處。接下來還有其它各種各樣的好處。當然,你不會僅僅想讓list擁有泛型。哈希表(hashtable)或者字典(dictionary)——隨便你怎么叫它——把鍵(keys)映射到值(values)。你可能會想要把strings映射到customrs,或者ints到orders,而且是以強類型化的方式。

c#的泛型
bill venners: 泛型在c#中是如何工作的?

anders hejlsberg: 沒有泛型的c#,基本上你只能寫class list {...}。有了泛型,你可以寫成class list<t> {...},這里t是類型參數(shù)。在list<t>范圍內(nèi)你可以把t當作類型來使用,當真正需要創(chuàng)建一個list對象的時候,寫成list<int>或者list<customer>。新類型是通過list<t>構(gòu)建的,實際上就像是你的類型參數(shù)替換掉了原本的類型參數(shù)。所有的t都變成了ints或者customers,你不需要做類型轉(zhuǎn)換,因為到處都會做強類型檢驗。

在clr(common language runtime)環(huán)境下,當編譯list<t>或者其它任何generic類型的時候,會像其它普通類型一樣,先編譯成中間語言il(intermediate language)以及元數(shù)據(jù)。理所當然,il以及元數(shù)據(jù)包含了額外的信息,從而可以知道有一個類型參數(shù),但是從原則上來說,generic類型的編譯與其它類型并沒有什么不同。在運行時刻,當應用程序第一次引用到list<int>的時候,系統(tǒng)會查找看是否有人已經(jīng)請求過list<int>。如果沒有,它會把list<t>的il和元數(shù)據(jù)以及類型參數(shù)int傳遞給jit。而jiter在即時編譯il的過程中,也會替換掉類型參數(shù)。

bruce eckel: 也就是說它是在運行時刻實例化的。

anders hejlsberg: 的確如此,它是在運行時刻實例化的。它在需要的時候產(chǎn)生出針對特定類型的原生代碼(native code)。從字面上看,當你說list<int>的時候,你會得到一個關于int的list。如果generic類型的代碼使用了一個關于t的array,你得到的就是一個關于int的array。

bruce eckel: 垃圾回收機制會在某個時候來回收它么?

anders hejlsberg: 可以說會,也可以說不會,這是一個正交的問題。這個類在應用程序范圍內(nèi)被創(chuàng)建,然后在這個應用程序范圍內(nèi)就一直存在下去。如果你殺掉這個應用程序,那么這個類也就消失了,這點跟其它類一樣。

bruce eckel: 如果我有一個應用程序用到了list<int>和list<cat>,但是它從來沒有走到使用list<cat>的那個分支。。。。。。

anders hejlsberg:。。。。。。那么系統(tǒng)就不會實例化一個list<cat>。現(xiàn)在讓我說說一些例外的情況。如果你是使用ngen在創(chuàng)建一個影像(image),也就是說你在直接產(chǎn)生一個native的映像,你可以提早產(chǎn)生這些實例。但是如果你是在通常的情況下運行程序,是否實例化是完全根據(jù)需要來確定的,而且推遲到越晚越好。

這之后,我們針對所有值類型(比如list<int>,list<long>,list<double>, list<float>)的實例化做進一步的處理,創(chuàng)建可執(zhí)行的原生代碼的唯一拷貝。這樣list<int>就有它自己的代碼。list<long>也有它自己的代碼。list<float>也是如此。對于所有引用類型(reference types),我們共享這些代碼,因為它們所代表的東西是相同的。它們只是一些指針罷了。

bruce eckel: 你需要進行類型轉(zhuǎn)換吧。

anders hejlsberg: 不,實際上并不需要。我們可以共享native image,但實際上它們有各自單獨的虛函數(shù)表(vtables)。我只是想指出,當共享代碼有意義的時候,我們會不遺余力的去做這件事情,但是當你非常需要運行效率的時候,我們對于共享代碼會非常謹慎。通常對于值類型,你確實會關心list<int>元素的類型就是int。你不想把它們裝箱(box)成objects。對值類型進行裝箱/拆箱,是可以用來進行代碼共享的一種方法,但是這種方法代價過于昂貴。

bill venners: 對于引用類型,實際上也是完全不同的類。list<elephant>和list<orangutan>是不同的,但是它們確實共享所有的類方法的代碼。

anders hejlsberg: 是的。作為實現(xiàn)上的細節(jié)來說,它們確實共享了相同的原生代碼(native code)。

c#泛型與java泛型的比較
bruce eckel: c#泛型相比java泛型有什么特點?

anders hejlsberg: java的泛型實現(xiàn)是基于一個最初叫做pizza的項目,這個項目是由martin odersky和其他一些人完成的。pizza被重新命名為gj,然后他成了一個jsr,并且最后被采納進了java語言。這個特定的泛型proposal有一個關鍵的設計目標,就是它應該能夠跑在不必經(jīng)過改動的虛擬機上。不用改動虛擬機當然很棒,但是它也帶來了一系列奇奇怪怪的限制。這些限制并不都是顯而易見的,但是很快你就會說,“hmm,這可有點怪。”

比如說,使用java泛型,實際上你就得不到任何剛才我所說得程序執(zhí)行上的效率,因為當你在java里編譯一個泛型類的時候,編譯器拿掉了類型參數(shù)并到處代之以object。list<t>編譯好的影像文件(image)就像是一個到處使用object(作為類型參數(shù))的list。當然,如果你試圖創(chuàng)建一個list<int>,那就的對所有用到的int對象進行裝箱(boxing)。這就產(chǎn)生了很大的負擔。此外,為了與老的虛擬機兼容,編譯器實際上會插入各種各樣的轉(zhuǎn)換代碼,而這些轉(zhuǎn)換代碼并不是由你來寫的。如果是一個關于object的list,而你試圖把這些objects當作customers來對待,這些objects必須在某些地方被轉(zhuǎn)換成customers,以便讓verifier的驗證能夠通過。實際上它們的實現(xiàn)所做的就是自動為你插入那些類型轉(zhuǎn)換。也就是說你得到了語法上的甜頭,或者至少是一部分語法上的甜頭,但是你并沒有得到任何程序執(zhí)行上的效率。這是我認為java泛型解決方案的第一個問題。

第二個問題是,我認為這可能是更大的一個問題,因為java的泛型實現(xiàn)依賴于去處掉類型參數(shù),當?shù)搅诉\行時刻,你實際上并沒有一個相對于運行時刻的可靠的泛型表示。當你在java里針對一個泛型list使用反射(reflection)的時候,你并不知道這個list到底是關于什么的list。它只是一個list。因為你已經(jīng)丟失了類型信息,對于任何動態(tài)代碼生成(dynamic code-generation)的應用或者基于反射的應用,就沒法工作了。這種趨勢對我來說已經(jīng)很明了了,(丟失類型信息的)情況越來越多。它根本沒辦法工作,因為你丟失了類型信息。而在我們的實現(xiàn)里,所有這些信息都是可獲得的。你可以通過反射得到list<t>對象的system.type表示。但這時候你還不能創(chuàng)建它的實例,因為你還不知道t是什么。但是你可以使用反射得到int的system.type表示。然后你可以請求反射機制把這兩個東西放在一起創(chuàng)建一個list<int>,這樣你就得到了另外一個用以表示list<int>的system.type。也就是說,從表示方法來說,任何你可以在編譯時刻做到的事情,你也可以在運行時刻做到。

c#泛型與c++模板的比較
bruce eckel: c#泛型相比c++模板有哪些特點?

anders hejlsberg: 在我看來,理解c#泛型與c++模板之間的差異最重要的一點就是:c#泛型實際上就像是類,除了它們有類型參數(shù)。而c++模板實際上就像是宏(macros),除了它們看起來像是類。

c#泛型與c++模板最大的不同之處在于類型檢驗發(fā)生的時間以及實例化的方式。首先,c#是在運行時刻實例化的,而c++ 是在編譯時刻或者可能是在link的時候。但是不管怎樣,c++模板實例化發(fā)生在程序運行之前。這是第一個不同之處。第二個不同之處在于,當你編譯generic類型的時候,c#對它進行強類型檢驗。對于像list<t>這樣未加限制的類型參數(shù)(unconstrained type parameter),類型t的值所能使用的方法僅限于object類型所包含的方法,因為只有這些方法才是通常我們保證能夠存在的方法。也就是說,在c#泛型里,我們保證你所實施于類型參數(shù)的任何操作都會成功。

c++正好與此相反。在c++里,你可以對一個類型參數(shù)做任何你想做的事情。但是當你對它進行實例化的時候,它有可能通不過,而你會得到一些非常難懂的錯誤信息。比如,你有一個類型參數(shù)t以及兩個t類型的變量,x和y,如果你寫成x+y,那你最好事先定義了用于兩個t型變量相加的+運算符,否則你會得到一些古怪的錯誤信息。所以從某種意義上說,c++模板實際上是非類型化的,或者說是弱類型化的。而c#泛型則是強類型化的。

c#泛型的constraints特性
bruce eckel: constraints在c#泛型里是如何工作的?

anders hejlsberg: 在c#泛型里,我們可以針對類型參數(shù)加一些限制條件(constraints)。還以list<t>為例,你可以寫成,class list<t> where t: icomparable。意思是t必須實現(xiàn)icomparable接口。

bruce eckel: 有意思的是在c++里限制條件是隱含的。

anders hejlsberg: 是的。在c#里,你也可以讓限制條件是隱含的。比如說我們有一個dictionary<k,v>,它有一個add方法,以k為鍵(key)v為值(value)。add方法的實現(xiàn)很可能需要把傳入的鍵與dictionary已有的鍵進行比較,而且它可能通過一個叫做icomparable的接口來做這個比較。一種方法是把key參數(shù)轉(zhuǎn)換成icomparable,然后調(diào)用compareto方法。當然,當你這么做的時候,你就已經(jīng)針對k類型和key參數(shù)創(chuàng)建了一個隱式的限制條件。如果傳入的key沒有實現(xiàn)icomparable接口,你就會得到一個運行時錯誤。但是實際上你并沒有在你的哪個方法里或者約定里明確表明key必須實現(xiàn)icomparable。而且你當然還得付出運行時刻類型檢測的代價,因為實際上你所做的是運行時刻的動態(tài)類型檢驗。

使用constraint,你可以把代碼里的動態(tài)檢驗提前,在編譯時刻或者加載的時候?qū)λM行驗證。當你指定k必須實現(xiàn)icomparable,這就隱含了一系列的東西。對于任何k類型的值,你都可以直接訪問接口方法,而不需要進行轉(zhuǎn)換,因為從語義上來說,在整個程序里k類型要實現(xiàn)這個接口,這一點是得到保證的。無論什么時候你想要創(chuàng)建該類型的一個實例,編譯器都會針對你給出的任何作為k參數(shù)的類型進行檢驗,看它是否實現(xiàn)了icomparable。如果沒有實現(xiàn),你會得到一個編譯時錯誤。或者如果你是利用反射來做的話,會得到一個異常。

bruce eckel: 你說到了編譯器以及運行時刻。

anders hejlsberg: 編譯器會做檢驗,但是你也可能是在運行時刻通過反射來做的,這時候就由系統(tǒng)來做檢驗。如前所述,任何你在編譯時刻可以做的事情,你都可以在運行時刻通過反射來做。

bruce eckel: 我是否可以寫一個模板函數(shù),或者換句話說,一個參數(shù)類型未知的函數(shù)?你們是在所做的是給容器加上更強的類型檢驗,但是我是否可以像在c++模板里那樣得到弱類型化的東西呢?比如說,我是否可以寫一個函數(shù),它以a a和b b作為參數(shù),然后我在代碼里就可以寫a+b?我是否可以不關心a和b是什么,只要它們有一個“+”運算符就可以了,因為我想要的是弱類型化。

anders hejlsberg: 你實際上問的是,通過constraints你到底能做到什么程度?與其它特性類似,如果把constraints發(fā)揮到極致,他可以變得異常復雜。仔細想想,其實constraints是一種模式匹配(pattern matching)的機制。你想要能指定,“該類型參數(shù)必須有一個接受兩個參數(shù)的構(gòu)造函數(shù),并且實現(xiàn)了+運算符,要有某個靜態(tài)方法,以及其它兩個非靜態(tài)方法,等等。”問題是,你想要這種模式匹配的機制復雜到哪種程度?

從什么也不做到功能全面的模式匹配,這是很大的一個范圍。我們認為什么也不做太說不過去了,而全面的模式匹配又會變得非常復雜,所以我們選擇了折衷的方式。我們允許你指定一個constraint,它可以是一個類、零個或者多個接口、以及叫做constructor constraint的東西。比如說,你可以指定“該類型必須實現(xiàn)ifoo和ibar接口,”或者“該類型必須繼承自基類x。”一旦你這么做了,我們會在所有地方做類型檢驗以確認該constraint是否為真,包括編譯時刻和運行時刻。任何由這個constraint所暗含的方法都可以通過類型參數(shù)的實例直接訪問。

另外,在c#里,運算符都是靜態(tài)成員函數(shù)。也就是說,一個運算符永遠不可能成為一個接口的成員函數(shù),因此一個接口限制條件(interface constraint)永遠不可能讓你指定一個“+”運算符。要指定一個“+”運算符,唯一的方法就是通過一個類限制條件(class constraint),這個類限制條件指定說必須繼承自某個類,比如說number類,因為number有一個“+”運算符。但是你不可能把它抽象成:“必須有一個+運算符”,然后由我們來以多態(tài)的方式解析它的實際含義。

bill venners: 你是通過類型,而不是簽名(signature)來實現(xiàn)限制條件的。

anders hejlsberg: 是的。

bill venners: 也就是說指定類型必須擴展某個類或者實現(xiàn)某些接口。

anders hejlsberg: 是的。本來我們可以走得更遠。我們確實考慮過走得更遠一些,但是那會非常復雜。并且我們不知道添加這些復雜性相對于你所獲得的微不足道的好處,是否值得。如果你想做的事情沒有被constraint系統(tǒng)直接支持,你可以借助于工廠模式(factory pattern)來完成。比如說,你有一個矩陣類matrix<t>,在這個matrix里你想定義一個標量積(dot product)方法。這當然意味著你最終需要理解如何把兩個t相乘,但你不能把它表達成一個constraint,至少如果t是int、double或者float的時候這樣做不行。但是你可以這么做:讓matrix接受一個calculator<t>這樣的參數(shù),然后在calculator<t>里聲明一個叫做multiply的方法。你實現(xiàn)這個方法并把它傳給matrix。

bruce eckel: calculator也是個參數(shù)化類型。

anders hejlsberg: 是的,它有點像factory模式。總之,是有辦法來做這些事情的。可能不如你想要的那么棒,但是任何事情都是有代價的。

bruce eckel: 嗯,我感覺c++模板像是一種弱類型化(weak typing)的機制。當你開始在它上面添加constraints的時候,你是在從弱類型化轉(zhuǎn)向強類型化(strong typing)。通常加入強類型化都會讓事情更加復雜。這像是一個頻譜。

anders hejlsberg: 你所意識到的類型化(typing)的問題,其實是一個撥盤(dial)。你把它撥的越高,程序員越覺得難受,但同時代碼更安全了。但是在兩個方向上你都有可能把它撥過頭。

 

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 略阳县| 安阳市| 黔南| 泰顺县| 海兴县| 曲周县| 淮滨县| 错那县| 巍山| 马山县| 彭州市| 思南县| 鹿泉市| 南郑县| 南丹县| 定安县| 苍梧县| 南安市| 西宁市| 资源县| 女性| 龙川县| 手游| 隆化县| 凌源市| 金门县| 林口县| 齐河县| 佛冈县| 利川市| 威远县| 宁陕县| 金秀| 丰都县| 孝昌县| 永吉县| 宜春市| 黑水县| 武清区| 齐齐哈尔市| 高州市|