c#值類型和引用類型詳析

1. 主要內容

             類型的基本概念

             值類型深入

             引用類型深入

             值類型與引用類型的比較及應用

2. 基本概念

C#中,變量是值還是引用僅取決於其數據類型。

C#的基本數據類型都以平臺無關的方式來定義,C#的預定義類型並沒有內置於語言中,而是內置於.NET Framework中。.NET使用通用類型系統(CTS)定義了可以在中間語言(IL)中使用的預定義數據類型,所有面向.NET的語言都最終被編譯爲 IL,即編譯爲基於CTS類型的代碼,

通用類型的系統的功能:

  • 建立一個支持跨語言集成、類型安全和高性能代碼執行的框架。
  • 提供一個支持完整實現多種編程語言的面向對象的模型。
  • 定義各語言必須遵守的規則,有助於確保用不同語言編寫的對象能夠交互作用。

 

    CTS_01           clrcts

例如,在C#中聲明一個int變量時,聲明的實際上是CTS中System.Int32的一個實例。這具有重要的意義:

  • 確保IL上的強制類型安全;
  • 實現了不同.NET語言的互操作性;
  • 所有的數據類型都是對象。它們可以有方法,屬性,等。例如:

int i; 
i = 1; 
string s; 
s = i.ToString();

 

CLR 支持兩種類型:值類型引用類型,

 

 

C#的所有值類型均隱式派生自System.ValueType:

  • 結構體:struct(直接派生於System.ValueType);
    • 數值類型:
      • 整 型:sbyte(System.SByte的別名),short(System.Int16),int(System.Int32),long (System.Int64),byte(System.Byte),ushort(System.UInt16),uint (System.UInt32),ulong(System.UInt64),char(System.Char);
      • 浮點型:float(System.Single),double(System.Double);
      • 用於財務計算的高精度decimal型:decimal(System.Decimal)。
    • bool型:bool(System.Boolean的別名);
    • 用戶定義的結構體(派生於System.ValueType)。
  • 枚舉:enum(派生於System.Enum);
  • 可空類型(派生於System.Nullable<T>泛型結構體,T?實際上是System.Nullable<T>的別名)。

值類型(Value Type),值類型實例通常分配在線程的堆棧(stack)上,並且不包含任何指向實例數據的指針,因爲變量本身就包含了其實例數據

 

C#有以下一些引用類型:

  • 數組(派生於System.Array)
  • 用戶用定義的以下類型:
    • 類:class(派生於System.Object);
    • 接口:interface(接口不是一個“東西”,所以不存在派生於何處的問題。Anders在《C# Programming Language》中說,接口只是表示一種約定[contract]);
    • 委託:delegate(派生於System.Delegate)。
  • object(System.Object的別名);
  • 字符串:string(System.String的別名)。

可以看出:

  • 引用類型與值類型相同的是,結構體也可以實現接口;
  • 引用類型可以派生出新的類型,而值類型不能;
  • 引用類型可以包含null值,值類型不能(可空類型功能允許將 null 賦給值類型);
  • 引用類型變量的賦值只複製對對象的引用,而不復制對象本身。而將一個值類型變量賦給另一個值類型變量時,將複製包含的值

2.1內存深入

2.2.1 內存機制

數據在內存中分配位置取決與該變量的數據類型,上圖可知值類型分配在線程的堆棧上,引用類型則分配在託管堆上,由GC控制回收,以下代碼和圖演示了引用類型和值類型的區別:

private static class ReferenceVsValue { 
      // Reference type (because of 'class') 
      private class SomeRef { public Int32 x; }

      // Value type (because of 'struct') 
      private struct SomeVal { public Int32 x; }

      public static void Go() { 
         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;                     // r1.x和r2.x都會更改

         v1.x = 9;                     // 只是更改v1.x,不會更改v2.x 
         Console.WriteLine(r1.x);      // 顯示 "8" 
         Console.WriteLine(r2.x);      // 顯示 "8" 
         Console.WriteLine(v1.x);      // 顯示 "9" 
         Console.WriteLine(v2.x);      // 顯示 "5"  
         //右半部分反映了在執行所有代碼之後的情況 
      } 
   } 
                                   圖5-1       圖解代碼執行時的內存分配情況

 GC01

SomeVal是用Struct來聲明的,而不是用常用的Class,在C#中用Struct聲明的是值類型,每個變量或者程序都有自己的堆棧,不同的變量不能公用一個內存地址因此上圖中SomeRef和SomeVal一定佔用了不同的堆棧,變量經過傳遞後,對v1變量改變時,顯然不會影響到v2的數據,可以看出,堆棧中的v1,v2包含其實際數據,而r1,r2則在堆棧中保存了其實例數據的引用地址,實際的數據保存在託管堆中,因此就有可能不同變量保存了 同一地址的數據引用,當從一個引用類型變量傳遞到另外一個相同的引用類型變量時,傳遞的是引用地址而不是實際的數據,所以改變一個變量的值會影響到另外一個變量的值,值類型與引用類型在內存中的分配是決定其應用不同的根本原因,由此可以容易的解釋爲什麼傳遞參數的時候,按值傳遞不會改變形參的值,而按地址傳遞會改變形參的值。

內存分配的幾點:

  • 值類型變量做爲局部變量時,該實例將被創建在堆棧上;而如果值類型變量作爲類型的成員變量時,它將作爲類型實例數據的一部分,同該類型的其他字段都保存在託管堆上,將在接下來的嵌套結構部分來詳細說明問題。

  • 引用類型變量數據保存在託管堆上,但是根據實例的大小有所區別,如下:如果實例的大小小於85000Byte時,則該實例將創建在GC堆上;而當實例大小大於等於85000byte時,則該實例創建在LOH(Large Object Heap)堆上。

2.2.2嵌套類型

嵌套結構就是在值類型中嵌套定義了引用類型,或者在引用類型變量中嵌套定義了值類型

    • 引用類型嵌套值類型

public class NestedValueinRef 

//aInt做爲引用類型的一部分將分配在託管堆上 
private int aInt; 
public NestedValueinRef 

//aChar則分配在該段代碼的線程棧上 
char achar = 'a'; 

}                                      圖5-2 內存分配圖可以表示爲:

      GC003

 

  • 值類型嵌套引用類型

            引用類型嵌套在值類型時,內存的分配情況爲:該引用類型將作爲值類型的成員變量,堆棧上將保存該成員的引用,而成員的實際數據還是保存在託管堆中.

              public struct NestedRefinValue 
                  { 
                          public MyClass myClass; 
                          public NestedRefinValue 
                      { 
                                myClass.X = 1; 
                                myClass.Y = 2; 
                      } 
                  }

                                    圖5-3 內存分配圖可以表示爲:

      GC05

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章