摘要
anders hejlsberg,c#的主架構(gòu)師,與bruce eckel和bill venners 談?wù)摿薱#和java的泛型、c++模板、c#的constraints特性以及弱類型化和強(qiáng)類型化的問題。
anders hejlsberg,微軟的一位杰出工程師,他領(lǐng)導(dǎo)了c#(發(fā)音是c sharp)編程語言的設(shè)計(jì)團(tuán)隊(duì)。hejlsberg首次躍上軟件業(yè)界舞臺(tái)是源于他在80年代早期為ms-dos和cp/m寫的一個(gè)pascal編譯器。不久一個(gè)叫做borland的非常年輕的公司雇傭了他并且買下了他的編譯器,從那以后這個(gè)編譯器就作為turbo pascal在市場(chǎng)上推廣。在borland,hejlsberg繼續(xù)開發(fā)turbo pacal并且在后來領(lǐng)導(dǎo)一個(gè)團(tuán)隊(duì)設(shè)計(jì)turbo pascal的替代品:delphi。1996年,在borland工作13年以后,hejlsberg加入了微軟,在那里一開始作為visual j++和windows基礎(chǔ)類庫(kù)(wfc)的架構(gòu)師。隨后,hejlsberg擔(dān)任了c#的主要設(shè)計(jì)者和.net框架創(chuàng)建過程中的一個(gè)主要參與者。現(xiàn)在,anders hejlsberg領(lǐng)導(dǎo)c#編程語言的后續(xù)開發(fā)。
2003年7月30號(hào),bruce eckel(《thinking in c++》以及《thinking in java》的作者)和bill venners(artima.com的主編)與anders hejlsberg在他位于華盛頓州redmond的微軟辦公室進(jìn)行了一次面談。這次訪談的內(nèi)容將分多次發(fā)布在artima.com以及bruce eckel將于今年秋天發(fā)布的一張音頻光碟上。在這次訪談中,anders hejlsberg談?wù)摿薱#語言和.net框架設(shè)計(jì)上的一些取舍。
· 在 第一部分:c#的設(shè)計(jì)過程中, hejlsberg談?wù)摿薱#設(shè)計(jì)團(tuán)隊(duì)所采用的流程,以及在語言設(shè)計(jì)中可用性研究(usability studies)和好的品味(good taste)相對(duì)而言的優(yōu)點(diǎn)。
· 在第二部分:checked exceptions的問題中, hejlsberg談?wù)摿艘褭z測(cè)異常(checked exceptions)的版本(versionability)問題和規(guī)模擴(kuò)展(scalability)問題。
· 在第三部分: 委托、組件以及表面上的簡(jiǎn)單性里,hejlsberg 談?wù)摿宋校╠elegates)以及c#對(duì)于組件的概念給予的頭等待遇。
· 在第四部分:版本,虛函數(shù)和覆寫里,hejlsberg解釋了談?wù)摿藶槭裁碿#的方法默認(rèn)是非虛函數(shù),以及為什么程序員必須顯式指定覆寫(override)。
在第五部分:契約和互操作性里,hejlsberg談?wù)摿薲ll hell、接口契約、strong anmes以及互操作的重要性。
在第六部分:inappropriate abstractions里, hejlsberg以及c#團(tuán)隊(duì)的其他成員談?wù)摿嗽噲D讓網(wǎng)絡(luò)透明的分布式系統(tǒng),以及試圖屏蔽掉數(shù)據(jù)庫(kù)的對(duì)象——關(guān)系映射。
在第七部分, hejlsberg比較了c#和java的泛型以及c++模板的實(shí)現(xiàn)方法,并且介紹了c#的constraints特性以及弱類型化和強(qiáng)類型化的問題。
泛型概述
bruce eckel: 能否就泛型做一個(gè)簡(jiǎn)短的介紹?
anders hejlsberg: 泛型的本質(zhì)就是讓你的類型能夠擁有類型參數(shù)。它們也被成為參數(shù)化類型(parameterized types)或者參數(shù)的多態(tài)(parametric polymorphism)。經(jīng)典的例子十九一個(gè)list集合類。list是一個(gè)方便易用的、可增長(zhǎng)的數(shù)組。它有一個(gè)排序方法,你可以通過索引來引用它的元素,等等。現(xiàn)今,如果沒有參數(shù)化類型,在使用數(shù)組或者lists之間就會(huì)有些別扭的地方。如果使用數(shù)組,你得到了強(qiáng)類型保證,因?yàn)槟憧梢远x一個(gè)關(guān)于customer的數(shù)組,但是你沒有可增長(zhǎng)性和那些方便易用的方法。如果你用的是list,雖然你得到了所有這些方便,但是卻喪失了強(qiáng)類型保證。你不能指定一個(gè)list是關(guān)于什么的list。它只是一個(gè)關(guān)于object的list。這會(huì)給你帶來一些問題。類型檢測(cè)必須在運(yùn)行時(shí)刻做,也就意味著沒有在編譯時(shí)刻對(duì)類型進(jìn)行檢測(cè)。即便是你塞給list一個(gè)customer對(duì)象然后試圖取出一個(gè)string,編譯器也不會(huì)有絲毫的抱怨。直到運(yùn)行時(shí)刻你才會(huì)發(fā)現(xiàn)他會(huì)出問題。另外,當(dāng)把基元類型(primitive type)放入list的時(shí)候,還必須對(duì)它們進(jìn)行裝箱(box)。基于上述所有這些問題,lists與arrays之間的這種不和諧的地方總是存在的。到底選擇哪個(gè),會(huì)讓你一直猶豫不決。
泛型的最大好處就是它讓你有了一個(gè)兩全其美的辦法(you can have your cake and eat it too),因?yàn)槟憧梢远x一個(gè)list<t>[讀作:list of t]。當(dāng)使用一個(gè)list的時(shí)候,你可以實(shí)實(shí)在在地知道這個(gè)list是關(guān)于什么類型的list,并且讓編譯器為你做強(qiáng)類型檢測(cè)。這只是它最直接的好處。接下來還有其它各種各樣的好處。當(dāng)然,你不會(huì)僅僅想讓list擁有泛型。哈希表(hashtable)或者字典(dictionary)——隨便你怎么叫它——把鍵(keys)映射到值(values)。你可能會(huì)想要把strings映射到customrs,或者ints到orders,而且是以強(qiáng)類型化的方式。
c#的泛型
bill venners: 泛型在c#中是如何工作的?
anders hejlsberg: 沒有泛型的c#,基本上你只能寫class list {...}。有了泛型,你可以寫成class list<t> {...},這里t是類型參數(shù)。在list<t>范圍內(nèi)你可以把t當(dāng)作類型來使用,當(dāng)真正需要?jiǎng)?chuàng)建一個(gè)list對(duì)象的時(shí)候,寫成list<int>或者list<customer>。新類型是通過list<t>構(gòu)建的,實(shí)際上就像是你的類型參數(shù)替換掉了原本的類型參數(shù)。所有的t都變成了ints或者customers,你不需要做類型轉(zhuǎn)換,因?yàn)榈教幎紩?huì)做強(qiáng)類型檢驗(yàn)。
在clr(common language runtime)環(huán)境下,當(dāng)編譯list<t>或者其它任何generic類型的時(shí)候,會(huì)像其它普通類型一樣,先編譯成中間語言il(intermediate language)以及元數(shù)據(jù)。理所當(dāng)然,il以及元數(shù)據(jù)包含了額外的信息,從而可以知道有一個(gè)類型參數(shù),但是從原則上來說,generic類型的編譯與其它類型并沒有什么不同。在運(yùn)行時(shí)刻,當(dāng)應(yīng)用程序第一次引用到list<int>的時(shí)候,系統(tǒng)會(huì)查找看是否有人已經(jīng)請(qǐng)求過list<int>。如果沒有,它會(huì)把list<t>的il和元數(shù)據(jù)以及類型參數(shù)int傳遞給jit。而jiter在即時(shí)編譯il的過程中,也會(huì)替換掉類型參數(shù)。
bruce eckel: 也就是說它是在運(yùn)行時(shí)刻實(shí)例化的。
anders hejlsberg: 的確如此,它是在運(yùn)行時(shí)刻實(shí)例化的。它在需要的時(shí)候產(chǎn)生出針對(duì)特定類型的原生代碼(native code)。從字面上看,當(dāng)你說list<int>的時(shí)候,你會(huì)得到一個(gè)關(guān)于int的list。如果generic類型的代碼使用了一個(gè)關(guān)于t的array,你得到的就是一個(gè)關(guān)于int的array。
bruce eckel: 垃圾回收機(jī)制會(huì)在某個(gè)時(shí)候來回收它么?
anders hejlsberg: 可以說會(huì),也可以說不會(huì),這是一個(gè)正交的問題。這個(gè)類在應(yīng)用程序范圍內(nèi)被創(chuàng)建,然后在這個(gè)應(yīng)用程序范圍內(nèi)就一直存在下去。如果你殺掉這個(gè)應(yīng)用程序,那么這個(gè)類也就消失了,這點(diǎn)跟其它類一樣。
bruce eckel: 如果我有一個(gè)應(yīng)用程序用到了list<int>和list<cat>,但是它從來沒有走到使用list<cat>的那個(gè)分支。。。。。。
anders hejlsberg:。。。。。。那么系統(tǒng)就不會(huì)實(shí)例化一個(gè)list<cat>。現(xiàn)在讓我說說一些例外的情況。如果你是使用ngen在創(chuàng)建一個(gè)影像(image),也就是說你在直接產(chǎn)生一個(gè)native的映像,你可以提早產(chǎn)生這些實(shí)例。但是如果你是在通常的情況下運(yùn)行程序,是否實(shí)例化是完全根據(jù)需要來確定的,而且推遲到越晚越好。
這之后,我們針對(duì)所有值類型(比如list<int>,list<long>,list<double>, list<float>)的實(shí)例化做進(jìn)一步的處理,創(chuàng)建可執(zhí)行的原生代碼的唯一拷貝。這樣list<int>就有它自己的代碼。list<long>也有它自己的代碼。list<float>也是如此。對(duì)于所有引用類型(reference types),我們共享這些代碼,因?yàn)樗鼈兯淼臇|西是相同的。它們只是一些指針罷了。
bruce eckel: 你需要進(jìn)行類型轉(zhuǎn)換吧。
anders hejlsberg: 不,實(shí)際上并不需要。我們可以共享native image,但實(shí)際上它們有各自單獨(dú)的虛函數(shù)表(vtables)。我只是想指出,當(dāng)共享代碼有意義的時(shí)候,我們會(huì)不遺余力的去做這件事情,但是當(dāng)你非常需要運(yùn)行效率的時(shí)候,我們對(duì)于共享代碼會(huì)非常謹(jǐn)慎。通常對(duì)于值類型,你確實(shí)會(huì)關(guān)心list<int>元素的類型就是int。你不想把它們裝箱(box)成objects。對(duì)值類型進(jìn)行裝箱/拆箱,是可以用來進(jìn)行代碼共享的一種方法,但是這種方法代價(jià)過于昂貴。
bill venners: 對(duì)于引用類型,實(shí)際上也是完全不同的類。list<elephant>和list<orangutan>是不同的,但是它們確實(shí)共享所有的類方法的代碼。
anders hejlsberg: 是的。作為實(shí)現(xiàn)上的細(xì)節(jié)來說,它們確實(shí)共享了相同的原生代碼(native code)。
c#泛型與java泛型的比較
bruce eckel: c#泛型相比java泛型有什么特點(diǎn)?
anders hejlsberg: java的泛型實(shí)現(xiàn)是基于一個(gè)最初叫做pizza的項(xiàng)目,這個(gè)項(xiàng)目是由martin odersky和其他一些人完成的。pizza被重新命名為gj,然后他成了一個(gè)jsr,并且最后被采納進(jìn)了java語言。這個(gè)特定的泛型proposal有一個(gè)關(guān)鍵的設(shè)計(jì)目標(biāo),就是它應(yīng)該能夠跑在不必經(jīng)過改動(dòng)的虛擬機(jī)上。不用改動(dòng)虛擬機(jī)當(dāng)然很棒,但是它也帶來了一系列奇奇怪怪的限制。這些限制并不都是顯而易見的,但是很快你就會(huì)說,“hmm,這可有點(diǎn)怪。”
比如說,使用java泛型,實(shí)際上你就得不到任何剛才我所說得程序執(zhí)行上的效率,因?yàn)楫?dāng)你在java里編譯一個(gè)泛型類的時(shí)候,編譯器拿掉了類型參數(shù)并到處代之以object。list<t>編譯好的影像文件(image)就像是一個(gè)到處使用object(作為類型參數(shù))的list。當(dāng)然,如果你試圖創(chuàng)建一個(gè)list<int>,那就的對(duì)所有用到的int對(duì)象進(jìn)行裝箱(boxing)。這就產(chǎn)生了很大的負(fù)擔(dān)。此外,為了與老的虛擬機(jī)兼容,編譯器實(shí)際上會(huì)插入各種各樣的轉(zhuǎn)換代碼,而這些轉(zhuǎn)換代碼并不是由你來寫的。如果是一個(gè)關(guān)于object的list,而你試圖把這些objects當(dāng)作customers來對(duì)待,這些objects必須在某些地方被轉(zhuǎn)換成customers,以便讓verifier的驗(yàn)證能夠通過。實(shí)際上它們的實(shí)現(xiàn)所做的就是自動(dòng)為你插入那些類型轉(zhuǎn)換。也就是說你得到了語法上的甜頭,或者至少是一部分語法上的甜頭,但是你并沒有得到任何程序執(zhí)行上的效率。這是我認(rèn)為java泛型解決方案的第一個(gè)問題。
第二個(gè)問題是,我認(rèn)為這可能是更大的一個(gè)問題,因?yàn)閖ava的泛型實(shí)現(xiàn)依賴于去處掉類型參數(shù),當(dāng)?shù)搅诉\(yùn)行時(shí)刻,你實(shí)際上并沒有一個(gè)相對(duì)于運(yùn)行時(shí)刻的可靠的泛型表示。當(dāng)你在java里針對(duì)一個(gè)泛型list使用反射(reflection)的時(shí)候,你并不知道這個(gè)list到底是關(guān)于什么的list。它只是一個(gè)list。因?yàn)槟阋呀?jīng)丟失了類型信息,對(duì)于任何動(dòng)態(tài)代碼生成(dynamic code-generation)的應(yīng)用或者基于反射的應(yīng)用,就沒法工作了。這種趨勢(shì)對(duì)我來說已經(jīng)很明了了,(丟失類型信息的)情況越來越多。它根本沒辦法工作,因?yàn)槟銇G失了類型信息。而在我們的實(shí)現(xiàn)里,所有這些信息都是可獲得的。你可以通過反射得到list<t>對(duì)象的system.type表示。但這時(shí)候你還不能創(chuàng)建它的實(shí)例,因?yàn)槟氵€不知道t是什么。但是你可以使用反射得到int的system.type表示。然后你可以請(qǐng)求反射機(jī)制把這兩個(gè)東西放在一起創(chuàng)建一個(gè)list<int>,這樣你就得到了另外一個(gè)用以表示list<int>的system.type。也就是說,從表示方法來說,任何你可以在編譯時(shí)刻做到的事情,你也可以在運(yùn)行時(shí)刻做到。
c#泛型與c++模板的比較
bruce eckel: c#泛型相比c++模板有哪些特點(diǎn)?
anders hejlsberg: 在我看來,理解c#泛型與c++模板之間的差異最重要的一點(diǎn)就是:c#泛型實(shí)際上就像是類,除了它們有類型參數(shù)。而c++模板實(shí)際上就像是宏(macros),除了它們看起來像是類。
c#泛型與c++模板最大的不同之處在于類型檢驗(yàn)發(fā)生的時(shí)間以及實(shí)例化的方式。首先,c#是在運(yùn)行時(shí)刻實(shí)例化的,而c++ 是在編譯時(shí)刻或者可能是在link的時(shí)候。但是不管怎樣,c++模板實(shí)例化發(fā)生在程序運(yùn)行之前。這是第一個(gè)不同之處。第二個(gè)不同之處在于,當(dāng)你編譯generic類型的時(shí)候,c#對(duì)它進(jìn)行強(qiáng)類型檢驗(yàn)。對(duì)于像list<t>這樣未加限制的類型參數(shù)(unconstrained type parameter),類型t的值所能使用的方法僅限于object類型所包含的方法,因?yàn)橹挥羞@些方法才是通常我們保證能夠存在的方法。也就是說,在c#泛型里,我們保證你所實(shí)施于類型參數(shù)的任何操作都會(huì)成功。
c++正好與此相反。在c++里,你可以對(duì)一個(gè)類型參數(shù)做任何你想做的事情。但是當(dāng)你對(duì)它進(jìn)行實(shí)例化的時(shí)候,它有可能通不過,而你會(huì)得到一些非常難懂的錯(cuò)誤信息。比如,你有一個(gè)類型參數(shù)t以及兩個(gè)t類型的變量,x和y,如果你寫成x+y,那你最好事先定義了用于兩個(gè)t型變量相加的+運(yùn)算符,否則你會(huì)得到一些古怪的錯(cuò)誤信息。所以從某種意義上說,c++模板實(shí)際上是非類型化的,或者說是弱類型化的。而c#泛型則是強(qiáng)類型化的。
c#泛型的constraints特性
bruce eckel: constraints在c#泛型里是如何工作的?
anders hejlsberg: 在c#泛型里,我們可以針對(duì)類型參數(shù)加一些限制條件(constraints)。還以list<t>為例,你可以寫成,class list<t> where t: icomparable。意思是t必須實(shí)現(xiàn)icomparable接口。
bruce eckel: 有意思的是在c++里限制條件是隱含的。
anders hejlsberg: 是的。在c#里,你也可以讓限制條件是隱含的。比如說我們有一個(gè)dictionary<k,v>,它有一個(gè)add方法,以k為鍵(key)v為值(value)。add方法的實(shí)現(xiàn)很可能需要把傳入的鍵與dictionary已有的鍵進(jìn)行比較,而且它可能通過一個(gè)叫做icomparable的接口來做這個(gè)比較。一種方法是把key參數(shù)轉(zhuǎn)換成icomparable,然后調(diào)用compareto方法。當(dāng)然,當(dāng)你這么做的時(shí)候,你就已經(jīng)針對(duì)k類型和key參數(shù)創(chuàng)建了一個(gè)隱式的限制條件。如果傳入的key沒有實(shí)現(xiàn)icomparable接口,你就會(huì)得到一個(gè)運(yùn)行時(shí)錯(cuò)誤。但是實(shí)際上你并沒有在你的哪個(gè)方法里或者約定里明確表明key必須實(shí)現(xiàn)icomparable。而且你當(dāng)然還得付出運(yùn)行時(shí)刻類型檢測(cè)的代價(jià),因?yàn)閷?shí)際上你所做的是運(yùn)行時(shí)刻的動(dòng)態(tài)類型檢驗(yàn)。
使用constraint,你可以把代碼里的動(dòng)態(tài)檢驗(yàn)提前,在編譯時(shí)刻或者加載的時(shí)候?qū)λM(jìn)行驗(yàn)證。當(dāng)你指定k必須實(shí)現(xiàn)icomparable,這就隱含了一系列的東西。對(duì)于任何k類型的值,你都可以直接訪問接口方法,而不需要進(jìn)行轉(zhuǎn)換,因?yàn)閺恼Z義上來說,在整個(gè)程序里k類型要實(shí)現(xiàn)這個(gè)接口,這一點(diǎn)是得到保證的。無論什么時(shí)候你想要?jiǎng)?chuàng)建該類型的一個(gè)實(shí)例,編譯器都會(huì)針對(duì)你給出的任何作為k參數(shù)的類型進(jìn)行檢驗(yàn),看它是否實(shí)現(xiàn)了icomparable。如果沒有實(shí)現(xiàn),你會(huì)得到一個(gè)編譯時(shí)錯(cuò)誤。或者如果你是利用反射來做的話,會(huì)得到一個(gè)異常。
bruce eckel: 你說到了編譯器以及運(yùn)行時(shí)刻。
anders hejlsberg: 編譯器會(huì)做檢驗(yàn),但是你也可能是在運(yùn)行時(shí)刻通過反射來做的,這時(shí)候就由系統(tǒng)來做檢驗(yàn)。如前所述,任何你在編譯時(shí)刻可以做的事情,你都可以在運(yùn)行時(shí)刻通過反射來做。
bruce eckel: 我是否可以寫一個(gè)模板函數(shù),或者換句話說,一個(gè)參數(shù)類型未知的函數(shù)?你們是在所做的是給容器加上更強(qiáng)的類型檢驗(yàn),但是我是否可以像在c++模板里那樣得到弱類型化的東西呢?比如說,我是否可以寫一個(gè)函數(shù),它以a a和b b作為參數(shù),然后我在代碼里就可以寫a+b?我是否可以不關(guān)心a和b是什么,只要它們有一個(gè)“+”運(yùn)算符就可以了,因?yàn)槲蚁胍氖侨躅愋突?/font>
anders hejlsberg: 你實(shí)際上問的是,通過constraints你到底能做到什么程度?與其它特性類似,如果把constraints發(fā)揮到極致,他可以變得異常復(fù)雜。仔細(xì)想想,其實(shí)constraints是一種模式匹配(pattern matching)的機(jī)制。你想要能指定,“該類型參數(shù)必須有一個(gè)接受兩個(gè)參數(shù)的構(gòu)造函數(shù),并且實(shí)現(xiàn)了+運(yùn)算符,要有某個(gè)靜態(tài)方法,以及其它兩個(gè)非靜態(tài)方法,等等。”問題是,你想要這種模式匹配的機(jī)制復(fù)雜到哪種程度?
從什么也不做到功能全面的模式匹配,這是很大的一個(gè)范圍。我們認(rèn)為什么也不做太說不過去了,而全面的模式匹配又會(huì)變得非常復(fù)雜,所以我們選擇了折衷的方式。我們?cè)试S你指定一個(gè)constraint,它可以是一個(gè)類、零個(gè)或者多個(gè)接口、以及叫做constructor constraint的東西。比如說,你可以指定“該類型必須實(shí)現(xiàn)ifoo和ibar接口,”或者“該類型必須繼承自基類x。”一旦你這么做了,我們會(huì)在所有地方做類型檢驗(yàn)以確認(rèn)該constraint是否為真,包括編譯時(shí)刻和運(yùn)行時(shí)刻。任何由這個(gè)constraint所暗含的方法都可以通過類型參數(shù)的實(shí)例直接訪問。
另外,在c#里,運(yùn)算符都是靜態(tài)成員函數(shù)。也就是說,一個(gè)運(yùn)算符永遠(yuǎn)不可能成為一個(gè)接口的成員函數(shù),因此一個(gè)接口限制條件(interface constraint)永遠(yuǎn)不可能讓你指定一個(gè)“+”運(yùn)算符。要指定一個(gè)“+”運(yùn)算符,唯一的方法就是通過一個(gè)類限制條件(class constraint),這個(gè)類限制條件指定說必須繼承自某個(gè)類,比如說number類,因?yàn)閚umber有一個(gè)“+”運(yùn)算符。但是你不可能把它抽象成:“必須有一個(gè)+運(yùn)算符”,然后由我們來以多態(tài)的方式解析它的實(shí)際含義。
bill venners: 你是通過類型,而不是簽名(signature)來實(shí)現(xiàn)限制條件的。
anders hejlsberg: 是的。
bill venners: 也就是說指定類型必須擴(kuò)展某個(gè)類或者實(shí)現(xiàn)某些接口。
anders hejlsberg: 是的。本來我們可以走得更遠(yuǎn)。我們確實(shí)考慮過走得更遠(yuǎn)一些,但是那會(huì)非常復(fù)雜。并且我們不知道添加這些復(fù)雜性相對(duì)于你所獲得的微不足道的好處,是否值得。如果你想做的事情沒有被constraint系統(tǒng)直接支持,你可以借助于工廠模式(factory pattern)來完成。比如說,你有一個(gè)矩陣類matrix<t>,在這個(gè)matrix里你想定義一個(gè)標(biāo)量積(dot product)方法。這當(dāng)然意味著你最終需要理解如何把兩個(gè)t相乘,但你不能把它表達(dá)成一個(gè)constraint,至少如果t是int、double或者float的時(shí)候這樣做不行。但是你可以這么做:讓matrix接受一個(gè)calculator<t>這樣的參數(shù),然后在calculator<t>里聲明一個(gè)叫做multiply的方法。你實(shí)現(xiàn)這個(gè)方法并把它傳給matrix。
bruce eckel: calculator也是個(gè)參數(shù)化類型。
anders hejlsberg: 是的,它有點(diǎn)像factory模式。總之,是有辦法來做這些事情的。可能不如你想要的那么棒,但是任何事情都是有代價(jià)的。
bruce eckel: 嗯,我感覺c++模板像是一種弱類型化(weak typing)的機(jī)制。當(dāng)你開始在它上面添加constraints的時(shí)候,你是在從弱類型化轉(zhuǎn)向強(qiáng)類型化(strong typing)。通常加入強(qiáng)類型化都會(huì)讓事情更加復(fù)雜。這像是一個(gè)頻譜。
anders hejlsberg: 你所意識(shí)到的類型化(typing)的問題,其實(shí)是一個(gè)撥盤(dial)。你把它撥的越高,程序員越覺得難受,但同時(shí)代碼更安全了。但是在兩個(gè)方向上你都有可能把它撥過頭。
新聞熱點(diǎn)
疑難解答
圖片精選