.NET實值型別和參考型別101

來源:互聯網
上載者:User
1.1.1 摘要

      什麼是實值型別?什麼是參考型別?這問題大家很久以前就討論和研究過了,而且我相信很多人對實值型別和參考型別的區別和用法都嫻熟於心。這裡我給出自己的總結,而且也提供大家一個複習實值型別和參考型別的機會。

      熟悉C/C++的程式員都知道你可以為任何類型的對象建立指標來引用它們。這也不是java,任何類型都自動聲明為參考型別,然後C#中的類型提供了實值型別和參考型別。

1.1.2 本文

圖1實值型別和參考型別大綱

      實值型別是用來存放對象的值,那麼參考型別就是存放對象的引用值,真的是這樣嗎?我看到很多人都說:“實值型別就是用來存放值,而且不能或不應該。存放方法和行為”在我看來實值型別不僅僅是存放值的,還是有存放方法和行為的,例如:DateTime和Decimal毫無疑問是實值型別,而且.NET為該類型提供了許多方法和行為,以致DateTime和Decimal的功能強大。

      實值型別的值存放在棧中,參考型別的對象存放在堆中,真的是這樣嗎?我覺得這要視乎語言而定,就像我們前面提到C/C++類型都被定義為實值型別,而Java中的類型都被定義為參考型別(前置條件一),而且我們不應該認為儲存地區是一成不變的,要看具體的語言,說不定猴年馬月C#會把一些參考型別放在棧中。

      還有參考型別對象存放在堆中是沒有問題的,但是實值型別的值就存放地區要根據實值型別的定義來確定,例如我們把一個實值型別分別定義為一個方法中局部變數和一個類中的欄位,存放地區就不一樣了,前著存放在棧中,而後者存放在堆中(前置條件二)。而且我相信在上學的時候老師講的最多是實值型別和參考型別存放地區—堆棧,以後要分別加上連個前置條件。

 

  • 根據具體的語言
  • 根據實值型別具體定義

圖2實值型別和參考型別儲存

      上面簡單地描述了實值型別和參考型別在記憶體中儲存的方式,實值型別資料存放區在棧中,參考型別的引用值存放在棧,而對象和資料值儲存在堆中,大家都清楚.NET中提供的預定義類型中除了object和string是參考型別外其他類型都是實值型別。讓我們通過具體的例子來講講實值型別和參考型別。

      OK首先我們定義一個Custom類,然後添加兩個欄位_index和_description,為了簡潔起見我們沒有使用屬性,具體例子如下:

/// <summary>/// A custom clase./// </summary>public class CustomType{    /// <summary>    /// _index is value type.    /// _description is reference.    /// </summary>    private int _index;    private string _description;}

    假設我們執行個體化該類型的一個對象,然後讓我們分析一下該對象在記憶體中是如何分配的,好現是畫畫的時間了。

  • 參考型別的對象和資料總是儲存在堆中
  • 實值型別和參考型別的引用值可以儲存在棧或堆視乎語言環境而定

 

圖3參考型別儲存方式

      通過我們可以發現參考型別的對象是存放在堆中這是毫無疑問的,而我們在CustomType中定義的實值型別_index也是存放在堆中,這充分說明一點就是實值型別的儲存地區要根據具體定義確定。

      接下來我們介紹一個實值型別--Struct,我們定義名為CustomType的結構體,然後添加兩個欄位和之前類中的一樣,現在又是畫畫的時間了。

 

圖4實值型別儲存方式

     現在我們執行個體化一個CustomType對象,由於我們清楚地知道CustomType是一個實值型別,所以它將被儲存在棧中,而且_index的值和_description的引用值都被儲存在棧中,_description具體的值或對象被儲存在堆中。

     通過前面的參考型別和實值型別對象在記憶體中儲存的例子,我們知道不能籠統地說:“實值型別儲存在棧中,參考型別儲存在堆中”,而是要加上前置的條件(語言類型,定義類型)。

     讓我們通過一段簡單的代碼向大家展示實值型別和參考型別之間的使用上的區別,這裡我們使用Struct和Class為例。

public class MyType{    /// <summary>    /// This class has two fields.    /// </summary>    private CustomType type1;    private CustomType type2;    /// <summary>    /// Initializes a new instance of the <see cref="MyType"/> class.    /// </summary>    public MyType()    {        this.type1 = new CustomType();        this.type2 = new CustomType();    }}
//// Instantiates a object of MyType.MyType myType = new MyType();

     現在讓大家分析一下當類型CustomType分別是結構體和類時記憶體配置空間的大小和分配次數(假設在32位CLR下)。

首先當CustomType是結構體時只需要一次記憶體配置,大小為CustomType類型大小的兩倍,由於CustomType大小為8Byte,則MyType分配空間大小為16Byte。如果CustomType是類時我們需要三次記憶體配置,一次是MyType對象的堆中分配,接著兩次分別是CustomType對象的堆中分配。

    再舉一個簡單的例子:

MyType[] var = new MyType[888];

     如果MyType是一個實值型別,則只需要一次分配,大小為 MyType對象大小的888倍。但如果MyType是一個參考型別,剛開始需要一次分配,分配後數組的各元素值為 null。如果再初始化數組中的每個元素,我們總共將需要執行889次分配——889 次分配要比 1次分配耗費更多的時間。分配許多參考型別對象將在堆空間上造成很多片段,從而降低系統的速度。

      很多人都說:“結構體是一個輕量級類”,他們這樣認為是根據結構體的效率比類要好,但我覺得這樣理解不夠全面,通過前面的兩個例子我們的確看到結構體的確效率優於類,因為實值型別不需要記憶體回收(除裝箱的實值型別之外),而且沒有類型識別開銷。但如果我們要進行的是大量的資料copy時候,實值型別要複製要初始化每個變數的值,而參考型別只需複製引用值就OK了。

MyType T1 = new MyType();MyType T2 = T1;

圖5實值型別參考型別賦值

      通過上面的例子我們發現當MyTpye是實值型別時我們需要複製N次而參考型別只需複製一次,如果複製資料量多時實值型別效率比參考型別效率要低。因為參考型別只需複製引用值就OK了。

實值型別和參考型別應用場合區別:

  • 該類型的主要職責是否用於資料存放區?
  • 該類型的公有介面是否完全由一些資料成員存取屬性定義?
  • 是否確信該類型永遠不可能有子類?
  • 是否確信該類型永遠不可能具有多態行為?

裝箱和拆箱

     Boxing是.NET中提供一種機制使得根據實值型別產生相應的對象,Unboxing就是把對象中的值取出來在放到相應的實值型別中。然我們通過一段簡單的例子來說明。

int i = 5;object o = i; //boxingint j = (int)o; //unboxing

    我們定義了一個實值型別i和一個參考型別o,當由實值型別i賦值給o時候發生了boxing,而由對象o賦值到j時候發生unboxing。但我們要注意的是如果我們boxing是long,而unboxing到int時候會拋出一個InvalidCastException異常。我們可以通過以下方式處理轉換異常問題,但這樣給代碼帶來了不必要的冗餘和精度缺失,所以我們在進行unboxing時應該和boxing變數類型一致。

long i = 5;
object o = i;  //boxing
int j = (int)(long)o; //unboxing

     通過前面的例子我們發現boxing和unboxing的轉換並沒有什麼標識,我們只可以根據實值型別和參考型別之間轉換判斷boxing和unboxing發生。為了協助大家理解這一點,考慮下面代碼boxing和unboxing操作的次數:

ArrayList list = new ArrayList();list.Add(22);list.Add(23);list.Add((int)list[0] + (int)list[1]);foreach (int i in list){    Console.WriteLine("Number is: {0}.\n", i);}

     首先我們往list數組中放入兩個實值型別,由於Add()方法接受的參數是object類型,所以需要進行兩次boxing操作。

     接著我們把list中的兩個對象取處理,然後轉換為實值型別進行相加操作這需要兩次unboxing操作,最後把結果指存放到list中要進行boxing操作。

     在遍曆list時候我們要把list中的值儲存到i中發生unboxing操作,接著我們調用Console. WriteLine (string format, object arg)要對i進行boxing操作。

     所以發生了4次boxing和3次unboxing操作。值得慶幸的是boxing和unboxing操作不多,對我們的程式效率暫時還沒有大的影響,但如果頻繁進行boxing和unboxing操作就會對我們程式產生不可估量的影響,這也是.NET提出要使用泛型的一個原因。

    上面的例子我覺得發生比較隱秘是在WriteLine()方法,因為平時使用太多反而沒有注意該方法傳遞的參數類型。

接下來我們通過一些更加隱秘的例子說明什麼時候會發生boxing操作。

int i = 23;i.ToString();     //Ai.GetHashCode();  //Bi.GetType();      //Ci.GetTypeCode();  //D

      請大家分析一下那個方法進行了boxing操作,激動人心的時刻又到了來讓我們公布答案--C。

原因很簡單Object.GetType()方法不可以重寫,所以實值型別類Integer沒有實現自己的GetType方法,只能通過boxing操作調用Object的GetType()方法。

圖6 MSIL代碼

 

1.1.3 總結

本文主要介紹什麼是實值型別和參考型別,和它們在記憶體中的分配我們從中看到了實值型別和參考型別的區別:

  • 參考型別存放對象的引用值,而不是對象本身
  • 實值型別存放是具體的資料本身
  • 很多時候實值型別比參考型別高效,但也有例外情況
  • 參考型別的Object Storage Service在堆中,而實值型別資料可以存放在棧或堆中,要根據具體的定義
  • 實值型別值可以通過boxing轉換為參考型別,通過unboxing轉換為實值型別

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.