關於Visual C 裝箱與拆箱的研究

2021-08-29 03:14:44 字數 4207 閱讀 6492

關於visual c#裝箱與拆箱的研究

在對這個問題展開討論之前,我們不妨先來問這麼幾個問題,以系統的了解我們今天要**的主題。

觀者也許曾無數次的使用過諸如system.console類或.net類庫中那些品種繁多的類。那麼,我想問的是它們究竟源自何處?c#又是如何聯絡它們?有沒有支援我們個性化擴充套件的機制或型別系統?又有哪些型別系統可供我們使用呢?如果我們這些pl們連這些問題都不知其然,更不知其所以然的話,c#之門恐怕會把我們拒之門外的。

那就讓我們先停停手中的活兒,理理頭緒,對作為.net重要技術和基礎之一的cts(common type system)做乙個饒有興趣的研究。顧名思義,cts就是為了實現在應用程式宣告和使用這些型別時必須遵循的規則而存在的通用型別系統。在這要插一句,雖然也許大家都對此再熟悉不過了,但是我還是要強調,.net將整個系統的型別分成兩大類 —— 值型別 和 引用型別。到此,你也許會怒斥:說了這麼半天,你似乎還沒有切入正題呢!別慌!知道了.net型別系統的的特點並不代表你真正理解了這個型別系統的原理和存在的意義。

大多數物件導向的語言都有兩種型別:原型別(語言固有的型別,如整數、列舉)和類。雖然在實現模組化和實體化方面,物件導向技術體現了很強的能力,但是也存在一些問題,比如現在提到的這個系統型別問題,歷史告訴我們兩組型別造成了許多問題。首先就是相容性問題,這個也是microsoft使勁抨擊的一點,多數的oo語言存在這個弱點,原因就是因為他們的原型別沒有共同的基點,於是他們在本質上並不是真正的物件,它們並不是從乙個通用基類裡派生來的。怪不得,anders heijlsberg 笑稱其為「魔術型別」。

cts值型別的乙個最大的特點是它們不能為null,言外之意就是值型別的變數總有乙個值。在c#中,它包括有原型別、結構、列舉器。這裡需要強調一點:在傳遞值型別的變數時,我們實際傳遞的是變數的值,而非底層物件的引用,這一點和傳遞引用型別的變數的情況截然不同;cts引用型別就好像是型別安全的指標,它可以為null。它包括 如類、介面、委託、陣列等型別。對比前面值型別的特點,當我們分配乙個引用型別時,系統會在後台的堆疊上分配乙個值(記憶體分配與位置)並返回對這個值的引用;當值為null時,說明沒有引用或型別指向某個物件。這就意味著,我們在宣告乙個引用型別的變數時,被操作的是此變數的引用(位址),而不是資料。

討論到這個地方的時候,本篇的主角終於閃亮登場了——欲**或者嘔吐的同志,請再忍耐一下。我想問乙個問題先:在使用這種多型別系統時如何有效的拓展和提高系統的效能?也許就是在黑板上對這個問題的**,西雅圖的那幫傢伙們提出了box(裝箱) and unbox(拆箱) 的想法。簡單的說。裝箱就是將值型別(value type)轉換為引用型別(reference type)的過程;反之,就是拆箱。(其實這種思想早八輩子就產生了)。下面我們就進一步詳細的討論裝箱和拆箱的過程。在討論中,我們剛剛提到的問題的答案也就迎刃而解了。

首先,我們先來看看裝箱過程,為此我們需要先做兩個工作:1、編寫例程; 2、開啟ildasm(msil**察看工具)為此我們先來看看以下的**:

**中,本篇我們只需要關注main()方法下加注釋的兩行**,第一行我們建立了乙個double型別的變數(dubbox)。顯然按規則,cts規定double是原型別,所以dubbox自然就是值型別的變數;第二行其實作了三個工作,這個將在下面的msil**中看的一清二楚。第一步取出dubbox的值,第二步將值型別轉換引用型別,第三步傳值給objbox。

msil**如下:

.method private hidebysig static void main(string args) cil managed

' and the boxed is "

il_0016: ldloc.0

il_0017: box [mscorlib]system.double

il_001c: ldloc.1

il_001d: callvirt instance string [mscorlib]system.object::tostring()

il_0022: call void [mscorlib]system.console::writeline(string,

object,

object)

il_0027: ret

} // end of method boxandunbox::main

在msil中,第il_0000 至 il_0010 行是描述前面兩行**的。參照c#的msil手冊,觀者不難理解這段底層**的執行過程,在這我著重描述一下當dubbox被裝箱時所發生的故事:(1)劃分堆疊記憶體,在堆疊上分配的記憶體 = dubbox的大小 + objbox及其結構所占用的空間;(2)dubbox的值(77.7699999999996)被複製到新近分配的堆疊中;(3)將分配給objbox的位址壓棧,此時它指向乙個object型別,即引用型別。

拆箱作為裝箱的逆過程,看上去好像很簡單,其實裡面多了很多值的思考的東西。首先,box的時候,我們不需要顯式的型別轉換,但是在unbox時就必須進行型別轉換。這是因為引用型別的物件可以被轉換為任何型別。(當然,這也是電腦和人腦乙個差別的體現)型別轉換不容迴避的將會受到來自cts管理中心的監控——其標準自然是依據規則。(其內容的容量足以專門設一章來討論)好了,我們還是先來看看下面這段**吧:

與前面裝箱的**相比,本段**多加了一行double dubunbox = (double)objbox;新加的這行**作了四個工作,這個也將體現在msil**中。第一步將乙個值壓入堆疊;第二步將引用型別轉換為值型別;第三步間接將值壓棧;第四步傳值給dubunbox。

msil**如下:

.method private hidebysig static void main(string args) cil managed

' and the unboxed is "

il_001e: ldloc.0

il_001f: box [mscorlib]system.double

il_0024: ldloc.2

il_0025: box [mscorlib]system.double

il_002a: call void [mscorlib]system.console::writeline(string,

object,

object)

il_002f: ret

} // end of method boxandunbox::main

在msil中,第il_0011 至 il_0018 行是描述新行**的。參照c#的msil手冊,觀者不難理解這段底層**的執行過程,在此我著重描述一下objbox在拆箱時的遭遇:(1)環境須先判斷堆疊上指向合法物件的位址,以及在對此物件向指定的型別進行轉換時是否合法,如果不合法,就丟擲異常;(2)當判斷型別轉換正確,就返回乙個指向物件內的值的指標。

看來,裝箱和拆箱也不過如此,費了半天勁,剛把『值』給裝到『箱』裡去了,有費了更多的勁把它拆解了,鬱悶啊!細心的觀者,可能還能結合**和msil看出,怎麼在呼叫console.writeline()的過程中又出現了兩次box,是的,我本想偷懶逃過這節,但是既然已被發現,就應該大膽的面對,其實這就是傳說中的「暗箱操作」啊! 因為console.writeline方法有許多的過載版本,此處的版本是以兩個string物件為引數,而具有object 型別的引數的過載是編譯器找到的最接近的版本,所以,編譯器為了求得與這個方法的原型一致,就必須對值型別的dubbox和dubunbox分別進行裝箱(轉換成引用型別)。

所以,為了避免由於無謂的隱式裝箱所造成的效能損失,在執行這些多型別過載方法之前,最好先對值進行裝箱。現在我們把上述地**改進為:

msil**:

.method private hidebysig static void main(string args) cil managed

' and the unboxed is "

il_0025: ldloc.1

il_0026: ldloc.3

il_0027: call void [mscorlib]system.console::writeline(string,

object,

object)

il_002c: ret

} // end of method boxandunbox::main

我暈!這算嘛事兒呀!看完後是不是該**的**,該上吊的上吊呀!相信能堅持到看完最後乙個 "!" 的同志一定是個好同志。

其實,我們也可以妄加揣測一下:引用型應當屬於高階型別,而值型屬於原始型別,箱只是乙個概念、乙個秩序、一套規則或準確說是乙個邏輯。原始的東西作為基礎,其複雜性和邏輯性不會很高,而高階的東西就不那麼穩定了,它會不斷的進化和發展,因為這個邏輯的『箱』會不斷地被要求擴充和完善。由此思路推演,我們就不難**出未來我們需要努力的方向和成功機會可能存在的地方—— !

關於Visual C 裝箱與拆箱的研究

關於visual c 裝箱與拆箱的研究 在對這個問題展開討論之前,我們不妨先來問這麼幾個問題,以系統的了解我們今天要 的主題。觀者也許曾無數次的使用過諸如system.console類或.net類庫中那些品種繁多的類。那麼,我想問的是它們究竟源自何處?c 又是如何聯絡 它們?有沒有支援我們個性化擴充...

關於Visual C 裝箱與拆箱的研究

關於visual c 裝箱與拆箱的研究 在對這個問題展開討論之前,我們不妨先來問這麼幾個問題,以系統的了解我們今天要 的主題。觀者也許曾無數次的使用過諸如system.console類或.類庫中那些品種繁多的類。那麼,我想問的是它們究竟源自何處?c 又是如何聯絡它們?有沒有支援我們個性化擴充套件的機...

裝箱與拆箱

總結來說 裝箱就是將值型別轉化成引用型別,拆箱就是就是將引用型別轉化成值型別 裝箱 int n 10 string s n.tostring 這個不是裝箱。string與int是完全不同的兩種型別,沒有父子類關係,所以不可能發生裝箱和拆箱,因為本身就不具備型別直接轉換的功能。console.writ...