CLR via C#讀書日記一' 引用類型和值類型'


 

CLR支持兩種類型:引用類型和值類型。

  引用類型總是在托管堆上分配的,C#的new操作符會返回對象的內存地址——也就是指向對象數據的內存地址。
  使用引用類型必須注意到一些問題:
  1)內存必須從托管堆上分配。
  2)對上分配的每個對象都有一些額外的成員(比如前面提到過得" 類型對象指針"和" 同步塊索引"),這些成員必須初始化。
  3)對象中的其他字節(為字段而設)總是設為零。
  4)從托管堆上分配一個對象時,可能強制執行一次垃圾回收操作。
  如果所有類型都是引用類型,應用程序的性能會顯著下降。為了提升簡單的、常用的類型的性能,CLR提供了名為"值類型"的輕量型類型。
  值類型的實例一般在線程棧上分配的(雖然也可作為字段嵌入一個引用類型的對象中)。在代表值類型的實例的一個變量中,並不包含一個指向實例的指針。相反,變量中包含了實例本身的字段。
  由於變量已經包含了實例的字段,所以為了操作實例中的字段,不再需要提供一個指針。值類型的實例不受垃圾回收器的控制。因此,值類型的使用緩解了托管堆中的壓力,並減少了一個應用程序在其生存期內需要進行的垃圾回收次數。
  .NET Framework SDK文檔明確指出,在查看一個類型時,任何稱為"類"的類型都是引用類型。如System.Exception類、System.Random類等引用類型。文檔將所有值類型都成為結構或枚舉。如System.Int32結構、System.Boolean結構等值類型。
  所有值類型都必須從System.ValueType派生。所有枚舉類型都從System.Enum抽象類派生,而System.Enum又是從System.ValueType派生的。CLR和所有編程語言都給予枚舉特殊待遇,以后會提到。
  所有值類型都是隱式密封的(sealed),目的是防止將一個值類型用於其他任何引用類型或值類型的基類型。
  在托管代碼中,要由定義類型的開發人員決定在什么地方分配類型的實例,使用該類型的人對此並無控制權。
  以下演示引用類型和值類型的區別:
//引用類型
class SomeRef
{
public Int32 x;
}
//值類型
struct SomeVal
{
public Int32 x;
}

static void Main(string[] args)
{
SomeRef r1
= new SomeRef(); //在堆上分配
SomeVal v1 = new SomeVal(); //在棧上分配
r1.x = 5;
v1.x
= 5;
Console.WriteLine(r1.x);
//5
Console.WriteLine(v1.x); //5

SomeRef r2
= r1;
SomeVal v2
= v1;
r1.x
= 8;
v1.x
= 9;
Console.WriteLine(r1.x);
//8
Console.WriteLine(r2.x); //8
Console.WriteLine(v1.x); //9
Console.WriteLine(v2.x); //5
}

 

  除非以下條件都能滿足,否則不應該將一個類型聲明成值類型:

  1)類型具有基元類型的行為。
  2)類型不需要從其他任何類型繼承
  3)類型也不會派生出其他類型。    
  類型實例的大小應該在考慮之列,因為默認情況下,實參是以傳值方式傳遞的,這會造成對值類型實例中的字段進行復制,從而影響性性能。同樣的,被定義為返回一個值類型的一個方法在返回時,實例中的字段會賦值到調用者分配的內存中,從而影響性能。
  所以,選用值類型還應滿足:
  1)類型的實例較小(約16字節或者更小)
  2)類型的實例較大(大於16字節),但不作為方法的實參傳遞,也不從方法返回。
  值類型的主要優勢在於它們不作為對象在托管堆上分配。
  值類型和引用類型的區別:
  1)值類型對象有兩種表示形式:未裝箱(unboxed)和已裝箱(boxed)。引用類型總是處於已裝箱形式。
  2)值類型是從System.ValueType派生的。該類型提供了與System.Object定義的相同的方法。然而,System.ValueType重寫了Equals方法和GetHashCode方法。由於這個默認實現存在性能問題,所以定義自己的值類型時,應該重寫Equals和GetHashCode方法,並提供它們的顯示實現。
  3)值類型的所有方法都不能是抽象的,而且所有方法都是隱式密封(sealed)方法。
  4)引用類型的變量包含的是堆上的一個對象的地址。默認情況,在創建一個引用類型的變量時,它被初始化為null,表明引用類型的變量當前不指向一個有效對象。相反,值類型初始化是,所有的成員都會初始化為0。由於值類型的變量不是指針,所以在訪問一個值類型時,不會拋出NullReferenceException異常。CLR確實提供了一個特殊的特性,能為值類型 添加"可空"標識。如"int?"
  5)  將一個值類型的變量賦給另一個值類型變量,會執行一次逐字段復制。將引用類型賦給另一個引用類型時,只復制內存地址。
  6)由於為裝箱的值類型不再堆上分配,所以一旦定義了該類型的一個實例的方法不再處於活動狀態,為他們分配的內存就會被釋放。這意味着值類型的實例在其內存被回收時,不會通過Finalize方法接收到一個通知。
 

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com 联系我们: