1. 程式人生 > >裝箱和拆箱-值型別和引用型別的區別

裝箱和拆箱-值型別和引用型別的區別

一、概述

在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.返回物件的地址,這個地址就是這個物件的引用。

拆箱的過程:

1.獲取已經裝箱的值型別例項的指標。

2.把獲取到的值複製到棧。

所以裝箱是比較耗費效能的,還有可能引發一次GC操作,而拆箱只是一個獲取指標的過程耗費資源要比裝箱小的多。注意:一個物件拆箱之後只能還原為原先未裝箱之前的型別,例如:你不能把int32型別裝箱後還原為int16型別。

我們在例項中分析下:

1.我們首先定義一個類,用來處理資料。

 

 

 2.在控制器中先定義兩個資料型別,賦值都為0。呼叫上面的類,看看會輸出什麼。

 

 

 前臺頁面(簡單例項,就直接用session了,平時可不要這麼寫)

 

 

 輸出頁面:

 

 

 這究竟是怎麼回事呢,引用型別的值為什麼會改變呢?下面我們詳細分析下:

 

首先,我們先要理解ref是什麼,對於Class型別使用 ref,是為了保持引用的地址是一致的。所以在使用引用引數時,必須在方法的宣告和呼叫中都使用ref修飾符。

如果還不清楚,就跟著程式碼走一遍吧!

1.在控制器中先打一個斷點,我們可以在區域性變數中看到他們的初始值都為0.

 

 

 

2.轉到方法Test2中,在還沒有開始修改引用來興值的時候,值還是0.

 

 

 3.在走過方法Test2後,我們看到引用型別資料值變為了5454.

 

 

 4.同樣操作,我們在Test方法後得到的值型別資料還是0。

 

 

 同樣是修改變數的值,為什麼值型別的卻並沒有改變呢?這就是他們各自的特性造成的了。傳遞引用引數的時候傳遞的是一個地址的值,在Test2方法內,形參a的地址被實參str給修改了,所以返回時,a的地址變了,由原先的指向0的地址變為了指向5454的地址,所以輸出了5454.而傳遞值型別引數的時候傳遞的是一個真實的值,他沒有地址,在Test方法內,形參b的值把這個值“0”拷貝一份,然後把拷貝後的值傳遞到了方法內部,所以,在方法內改變的只是拷貝的值,方法結束後b的值還是0.

 

在下面的視窗可以更加直觀的看出,變數a的地址在變化。

&n