在對這個問題展開討論之前,我們不妨先來問這麼幾個問題,以系統的了解我們今天要探究的主題。
觀者也許曾無數次的使用過諸如System.Console類或.NET類庫中那些品種繁多的類。那麼,我想問的是它們究竟源自何處?C#又是如何聯系它們?有沒有支持我們個性化擴展的機制或類型系統?又有哪些類型系統可供我們使用呢?如果我們這些PL們連這些問題都不知其然,更不知其所以然的話,C#之門恐怕會把我們拒之門外的。
那就讓我們先停停手中的活兒,理理頭緒,對作為.NET重要技術和基礎之一的CTS(Common Type System)做一個饒有興趣的研究。顧名思義,CTS就是為了實現在應用程序聲明和使用這些類型時必須遵循的規則而存在的通用類型系統。在這要插一句,雖然也許大家都對此再熟悉不過了,但是我還是要強調,.Net將整個系統的類型分成兩大類 —— 值類型 和 引用類型。到此,你也許會怒斥:說了這麼半天,你似乎還沒有切入正題呢!別慌!知道了.Net類型系統的的特點並不代表你真正理解了這個類型系統的原理和存在的意義。
大多數面向對象的語言都有兩種類型:原類型(語言固有的類型,如整數、枚舉)和類。雖然在實現模塊化和實體化方面,面向對象技術體現了很強的能力,但是也存在一些問題,比如現在提到的這個系統類型問題,歷史告訴我們兩組類型造成了許多問題。首先就是兼容性問題,這個也是Microsoft使勁抨擊的一點,多數的OO語言存在這個弱點,原因就是因為他們的原類型沒有共同的基點,於是他們在本質上並不是真正的對象,它們並不是從一個通用基類裡派生來的。怪不得,Anders Heijlsberg 笑稱其為“魔術類型”。
正是由於這一缺陷,當我們希望指定一個可以接受本語言支持的任何類型的參數的Method時,同樣的問題再次襲擾我們的大腦——不兼容。當然,對於C++的PL大拿,也許這個沒有什麼大不了的,他們會自豪的說,只要用重載的構造器為每一種原類型編寫一個Wrapper Class 不就完了嘛!好吧,這樣總算是能共存了,但是,接下來我們怎麼從這個魔術中得到我們最關心的東東 —— 結果呢?於是,他們依然會自信的打開Boarland,熟練的編寫一個重載過的函數來從剛才的那個 Wrapper Class 中獲取結果。兄弟 or 姐妹們 ,在當時的歷史條件下,你們的行為是創舉,但是相對於現在,你將會為此付出代價 —— 效率低下。畢竟,C++更依賴於對象,而非面向對象。承認現實總比死要面子更理智一些!花這麼大力氣,總算把鋪墊說完了,我想說的是:.Net環境的CTS 給我們帶來了方便。第一、CTS中的所有東西都是對象;第二、所有的對象都源自一個基類——System.Object類型。這就是所謂的單根層次結構(singly rooted hierarchy)關於System.Object的詳細資料請參考微軟的技術文檔。這裡我們簡略的談談上面提到過的兩大類型:Value Type 和 Reference Type。
CTS值類型的一個最大的特點是它們不能為null,言外之意就是值類型的變量總有一個值。在C#中,它包括有原類型、結構、枚舉器。這裡需要強調一點:在傳遞值類型的變量時,我們實際傳遞的是變量的值,而非底層對象的引用,這一點和傳遞引用類型的變量的情況截然不同;CTS引用類型就好像是類型安全的指針,它可以為null。它包括 如類、接口、委托、數組等類型。對比前面值類型的特點,當我們分配一個引用類型時,系統會在後台的堆棧上分配一個值(內存分配與位置)並返回對這個值的引用;當值為null時,說明沒有引用或類型指向某個對象。這就意味著,我們在聲明一個引用類型的變量時,被操作的是此變量的引用(地址),而不是數據。
討論到這個地方的時候,本篇的主角終於閃亮登場了——欲吐血或者嘔吐的同志,請再忍耐一下。我想問一個問題先:在使用這種多類型系統時如何有效的拓展和提高系統的性能?也許就是在黑板上對這個問題的探討,西雅圖的那幫家伙們提出了Box(裝箱) and UnBox(拆箱) 的想法。簡單的說。裝箱就是將值類型(value type)轉換為引用類型(reference type)的過程;反之,就是拆箱。(其實這種思想早八輩子就產生了)。下面我們就進一步詳細的討論裝箱和拆箱的過程。在討論中,我們剛剛提到的問題的答案也就迎刃而解了。