程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> [CLR via C#]5.3 值類型的裝箱和拆箱

[CLR via C#]5.3 值類型的裝箱和拆箱

編輯:C#入門知識

  在CLR中為了將一個值類型轉換成一個引用類型,要使用一個名為裝箱的機制。

  下面總結了對值類型的一個實例進行裝箱操作時內部發生的事:   1)在托管堆中分配好內存。分配的內存量是值類型的各個字段需要的內存量加上托管堆上的所有對象都有的兩個額外成員(類型對象指針同步塊索引)需要的內存量。   2)值類型的字段復制到新的分配的堆內存。   3)返回對象的地址。現在,這個地址是對一個對象的引用,值類型現在是一個引用類型。   拆箱不是直接將裝箱過程倒過來。拆箱的代價比裝箱低得多。拆箱其實就是一個獲取一個指針的過程,該指針指向包含在一個對象中的原始值類型(數據字段)。事實上,指針指向的是已裝箱實例中的未裝箱部分。所以,和裝箱不同,拆箱不要求在內存中復制字節。還有一個重點就是,拆箱之後,往往會緊接著發生一次字段的復制操作。   在對一個對象進行拆箱的時候,只能將其轉型為原始未裝箱時的值類型,如下:
  = = x= (Int16) o;
}

  = = x;             
    Int16 y = (Int16) (Int32) o; 
}

  在進行一次拆箱後,經常會緊接著一次字段的復制。以下演示了拆箱和復制操作:

  
    p.x = p.y =  o = p;          
    p = (Point) o;         
}

  在最後一行,C#編譯器會生成一條IL指令對o進行拆箱,並生成另一條IL指令將這些字段從堆復制到基於棧的變量p中。

  在看個演示裝箱和拆箱的例子:  

   Main(= ;            
     Object o = v;           
     v = ;                
     Console.WriteLine(v +  + (Int32)o);  
}

  你可以看出上述代碼進行了幾次裝箱操作?如果說是3次,你會不會意味呢?我們來看下生成的IL代碼。  

.method  hidebysig   Main(
    .maxstack ] 
    L_0001: ldc.i4. 

    
    L_0003: ldloc. 

    
    L_000a: ldc.i4.s  

    
    L_000d: ldloc.
    L_0013: ldstr 

    
    L_0018: ldloc.

    L_0023: call  [mscorlib]System.String::Concat(, , 
    L_0028: call 

  提示:主要原因是在Console.WriteLine方法上。

  Console.WriteLine方法要求獲取一個String對象,為了創建一個String對象,C#編譯器生成的代碼來調用String對象的靜態方法Concate。該方法有幾個重載的版本,唯一區別就是參數數量,在本例中需要連接三個數據項來創建一個字符串,所以編譯器會選擇以下Concat方法來調用:

  String Concat(Objetc arg0, Object arg1, Onject arg2);

  所以,如果像下面寫對WriteLine的調用,生成的IL代碼將具有更高的執行效率:

Console.WriteLine(v +  + o);  

  這只是移除了變量o之前的(Int32)強制轉換。就避免了一次拆箱和一次裝箱。

  我們還可以這樣調用WriteLine,進一步提升上述代碼的性能:

Console.WriteLine(v.ToString() +  + o);  

  現在,會為未裝箱的值類型實例v調用ToString方法,它返回一個String。String類型已經是引用類型,所以能直接傳給Concat方法,不需要任何裝箱操作。

  下面在演示一個裝箱和拆箱操作:  

   Main(= ;                  
     Object o = v;                
     v = ;                       
     Console.WriteLine(v)   
     v = (Int32) o;               
     Console.WriteLine(v);  
}

  上述代碼發生了多少次裝箱呢?答案是一次。因為System.Console類定義了獲取一個Int32作為參數的WriteLine方法的重載版本:  

  String Concat(Int32 value);

  在WriteLine方法內部也許會發生裝箱操作,但這已經不是我們能控制的。我們已經盡可能地從自己的代碼中消除了裝箱操作。

  最後,如果知道自己寫的代碼會造成編譯器反復對一個值類型進行裝箱,請改用手動方式對值類型進行裝箱。

  

 
         (  == obj )  
         

  下面展示了如何在內部正確實現一個Equals方法。   1)如果obj實參為null,就返回false,因為在調用非靜態的Equals方法時,this所標識的當前對象顯然不為null.   2)如果this和obj實參引用同一個對象,就返回true。在比較包含大量字段的對象時,這一步有助性能提升。   3)如果this和obj實參引用不同類型的對象,就返回false。一個String對象顯然不等於一個FileStream對象。   4)針對類型定義的每個實例字段,將this對象中的值與obj對象中的值進行比較。任何字段不相等,就返回false。   5)調用基類的Equals方法,以便比較它定義的任何字段。如果基類的Equals方法返回false,就返回false;否則返回true; 例如:
  
         (obj ==  )  
         (.GetType() != obj.GetType())  
         

  ==

  System.ValueType(所有值類型的基類)重寫了Object的Equals方法,並進行了正確的實現來執行值得相等性檢查。  

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved