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

待續….


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM