一、概述 在C#中,數據根據變數的類型以兩種方式中的一種存儲在一個變數中。變數的類型分為兩種:引用類型和值類型,這也是CLR支持的兩種類型。 二、定義 1.引用類型: 分配在堆上的類型稱為引用類型。 解析:一個可以稱為”類“的類型都是引用類型。 引用類型總是從托管堆上分配的,常用的語法就是New X ...
一、概述
在C#中,數據根據變數的類型以兩種方式中的一種存儲在一個變數中。變數的類型分為兩種:引用類型和值類型,這也是CLR支持的兩種類型。
二、定義
1.引用類型:
分配在堆上的類型稱為引用類型。
解析:一個可以稱為”類“的類型都是引用類型。 引用類型總是從托管堆上分配的,常用的語法就是New XX(). C#的new 操作符會返回對象的指針 - 也就是指向對象數據的記憶體地址的一個引用。引用類型的傳遞其實傳遞的是對象的指針(string類型比較特殊),所以在特定的場景下性能是高於值類型的。一個引用類型在創建時預設為null,也就是說當前變數不指向一個有效的對象,也就是我們常遇到的異常“未將對象引用設置到對象的實例”。
2.值類型:
值類型一般線上程棧上分配。
三、區別
我們總圖然後詳細分析。
1.值類型的數據存儲在記憶體的棧中,記憶體分配是自動釋放,在GC的控制之外,不會對GC造成壓力,所以值類型存取速度快;引用類型的數據存儲在記憶體的堆中,在.NET中會有GC來釋放,而記憶體單元中只存放堆中對象的地址,在.NET中會有GC來釋放所以存取速度慢。我們可以這麼理解,值類型就是現金,要用直接用;引用類型是存摺,要用還得先去銀行取現。
當然,值類型雖然存取速度快,但也不能卵用,舉個例子:我自定義一個struct 類型作為一個方法的參數會發生什麼呢?每次調用都會發生全欄位的賦值,這是不可接受的,這也是典型的值類型勿用場景。
2.值類型表示實際數據,引用類型表示指向存儲在記憶體堆中的數據的指針或引用。
3.值類型繼承自System.ValueType,引用類型繼承自System.Object。
4.值類型總是包含一個值,而引用類型可以是null。
四、封箱和拆箱
封箱(boxing)是把值類型轉換為引用類型(System.Object)。拆箱(unboxing)是相反的轉換過程。
封箱的過程:
1.在托管堆中分配好記憶體,分配的記憶體量是值類型的各個欄位需要的記憶體量加上托管堆上所以對象的兩個額外成員(類型對象指針,同步塊索引)需要的記憶體量。
2.值類型的欄位複製到新分配的堆記憶體中。
3.返回對象的地址,這個地址就是這個對象的引用。
從圖可知,對象 o 存的是地址引用,指向的是堆上的值,這個值的類型和變數 i 一樣,也是 int 類型,值(123)也就是從棧上變數 i複製過來的一個副本值而已。(所以裝箱就是在堆上分配好記憶體,再複製棧上的值,再將堆的地址引用返回到棧上)
拆箱的過程:
1.獲取已經裝箱的值類型實例的指針。
2.把獲取到的值複製到棧。
所以裝箱是比較耗費性能的,還有可能引發一次GC操作,而拆箱只是一個獲取指針的過程耗費資源要比裝箱小的多。註意:一個對象拆箱之後只能還原為原先未裝箱之前的類型,例如:你不能把int32類型裝箱後還原為int16類型。
引用類型和值類型區別實例:
1.我們首先定義一個類,用來處理數據。
2.在控制器中先定義兩個數據類型,賦值都為0。調用上面的類,看看會輸出什麼。
前臺頁面(簡單實例,就直接用session了,平時可不要這麼寫)
輸出頁面:
這究竟是怎麼回事呢,引用類型的值為什麼會改變呢?下麵我們詳細分析下:
首先,我們先要理解ref是什麼,對於Class類型使用 ref,是為了保持引用的地址是一致的。所以在使用引用參數時,必須在方法的聲明和調用中都使用ref修飾符。
如果還不清楚,就跟著代碼走一遍吧!
1.在控制器中先打一個斷點,我們可以在局部變數中看到他們的初始值都為0.
2.轉到方法Test2中,在還沒有開始修改引用類型的值的時候,a的值還是0.
3.在走過方法Test2後,我們看到引用類型a的值變為了5454.
4.同樣操作,我們在Test方法後得到的值類型數據還是0。
同樣是修改變數的值,為什麼值類型的卻並沒有改變呢?這就是他們各自的特性造成的了。傳遞引用參數的時候傳遞的是一個地址的值,在Test2方法內,形參a的地址被實參str給修改了,所以返回時,a的地址變了,由原先的指向0的地址變為了指向5454的地址,所以輸出了5454.而傳遞值類型參數的時候傳遞的是一個真實的值,他沒有地址,在Test方法內,形參b的值把這個值“0”拷貝一份,然後把拷貝後的值傳遞到了方法內部,所以,在方法內改變的只是拷貝的值,方法結束後b的值還是0.
在下麵的視窗可以更加直觀的看出,變數a的地址在變化。