国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

從CLR角度來看值類型與引用類型

2019-11-17 02:26:44
字體:
來源:轉載
供稿:網友

從CLR角度來看值類型與引用類型

前言

  本文中大部分示例代碼來自于《CLR via C# Edition3》,并在此之上加以總結和簡化,文中只是重點介紹幾個比較有共性的問題,對一些細節不會做過深入的講解。

前幾天一直忙著翻譯有關內存中堆和棧的問題博文《C#堆vs棧》,正是在寫作本文的過程中對有些地方還是產生了很多的Why,所以就先翻譯并學習了一些C/C++方面的知識,這樣有助于解決CLR之外的一些困惑,希望多大家有所幫助。

  對知識的理解上難免有偏差或不正確,如有疑問以及錯誤,還請大家回復~~~

值類型和引用類型的不同

  C#中的類型劃分為值類型(Value Type)和引用類型(Reference Type)。

Sample One:

        public void SampleOne()        {            SomeClass r1 = new SomeClass(); // 引用類型,在托管堆上被分配。            SomeStruct v1 = new SomeStruct(); // 值類型,在線程棧上被分配。            r1.X = 5; // 修改引用類型的值為5            v1.X = 5; // 修改值類型的x值為5            Console.WriteLine(r1.X); // 結果顯示“5”            Console.WriteLine(v1.X); // 結果顯示“5”        }

  SampleOne中僅僅是將值類型、引用類型的內容簡單地更改了,并沒有什么特別之處,對應的下圖是程序在線程棧(Thread Stack)和托管堆(Managed Heap)上的示意圖,唯一要說明的是下圖中的“r1”本身代表了“對托管堆中一個對象SomeClass的引用(指針==值類型)”,如果理解起來有難度,請參考文章《C#堆vs棧》 。

  

Sample Two:

  

        public void SmapleTwo()        {            SomeClass r1 = new SomeClass(); // 引用類型,在托管堆上被分配。            SomeStruct v1 = new SomeStruct(); // 值類型,在線程棧上被分配。            r1.X = 5; // 修改引用類型的值為5            v1.X = 5; // 修改值類型的x值為5            SomeClass r2 = r1; // 只復制引用(r1指針)            SomeStruct v2 = v1; // 復制v1生成棧上的新對象v2            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”        }

  SampleTwo中想說明的結果是:值類型是拷貝行為,新對象在棧上,新舊對象之間沒有影響;引用類型拷貝的是“指向對象的指針”,指針指向的地址內容還是同一個,所以改變r1.X將影響r2.X。

裝箱和拆箱

  上面一節講述了C#中值類型和引用類型在行為上是有區別的,值類型的“Box”操作而引發的一系列效率上的討論。

為什么會有裝箱和拆箱操作

  Framework中很多函數的原型被設計成參數為Object,這樣就導致了很多值類型需要轉換成Object引用類型,從而導致了裝箱以及稍后的拆箱操作(當然也有很多原型實現了值類型的參數重載或泛型方法,從而避免裝箱/拆箱操作)。

  舉一個例子:System.Collection.ArrayList的Add方法原型為public virtual int Add(object value);

  首先將線程棧中p逐字段的復制到托管堆中,并且產生了類型對象指針和同步索引快兩個對象,然后將類型對象指針p’返回給Add方法,如下圖:

  而新產生的托管堆中的對象完全不依賴于線程棧中原有的對象p,并且兩者的生命周期也沒有任何關聯。

  最后,當我們使用var p =(Point)a[0]的時候,將object轉換成Point類型,進行拆箱。進行拆箱的過程與裝箱相反:由托管堆中的引用類型復制到線程棧中作為值類型,再使用。

顯然,這里的Box和UnBox操作都會對程序的性能產生不利的影響,我們要避免此類問題的發生。

  用IL來給假設證明

Sample One:

        public int RunSample1()        {            var v = 5;            object o = v; //Box            v = 123;            Console.WriteLine(v + ", " + (Int32) o); //Fisrt 'v' boxed, and 'o' boxed.            return v;            #region IL Generate Code            //.method public hidebysig instance void  RunSample1() cil managed            //{            //  // 代碼大小       47 (0x2f)            //  .maxstack  3            //  .locals init ([0] int32 v,            //           [1] object o)            //  IL_0000:  nop            //  IL_0001:  ldc.i4.5            //  IL_0002:  stloc.0            //  IL_0003:  ldloc.0            //  IL_0004:  box        [mscorlib]System.Int32            //  IL_0009:  stloc.1            //  IL_000a:  ldc.i4.s   123            //  IL_000c:  stloc.0            //  IL_000d:  ldloc.0            //  IL_000e:  box        [mscorlib]System.Int32            //  IL_0013:  ldstr      ", "            //  IL_0018:  ldloc.1            //  IL_0019:  unbox.any  [mscorlib]System.Int32            //  IL_001e:  box        [mscorlib]System.Int32            //  IL_0023:  call       string [mscorlib]System.String::Concat(object,            //                                                              object,            //                                                              object)            //  IL_0028:  call       void [mscorlib]System.Console::WriteLine(string)            //  IL_002d:  nop            //  IL_002e:  ret            //} // end of method BoxAndUnBox::Run            #endregion        }

  從SampleOne中我們得到如下結論:

  1. 值類型v賦值給引用類型o的時候發生了裝箱操作,這就是我們所說的當值類型向引用類型轉換時產生裝箱操作(IL_0004),這是明顯的。
  2. System.String.Concat方法在本段代碼中是使用了需要三個Object的重載,所以v發生了裝箱操作(IL_0023)。
  3. 執行(Int32)o則強制執行了拆箱方法(IL_0019),而最終Concat重載還是需要Object類型,所以會再次對o進行裝箱操作(IL_001e)

  Sample Two:

        public Point RunSample2()        {            var p = new Point(1, 1);            Console.WriteLine(p); //Box, show 1,1            p.Offset(2, 2); //Change Point -> 3,3            Console.WriteLine(p); //Box, show 3,3            object o = p; //Box            Console.WriteLine(o); //Show 3,3            ((Point) o).Offset(3, 3); //UnBox and Change Point -> 6,6            Console.WriteLine(o); //Show 3,3            return (Point) o; // UnBox and Copy a instance for return                      #region IL Generate Code            //.method public hidebysig instance valuetype [System.Drawing]System.Drawing.Point             //        RunSample2() cil managed            //{            //  // 代碼大小       94 (0x5e)            //  .maxstack  3            //  .locals init ([0] valuetype [System.Drawing]System.Drawing.Point p,            //           [1] object o,            //           [2] valuetype [System.Drawing]System.Drawing.Point CS$1$0000,            //           [3] valuetype [System.Drawing]System.Drawing.Point CS$0$0001)            //  IL_0000:  nop            //  IL_0001:  ldloca.s   p            //  IL_0003:  ldc.i4.1            //  IL_0004:  ldc.i4.1            //  IL_0005:  call       instance void [System.Drawing]System.Drawing.Point::.ctor(int32,            //                                                                                 int32)            //  IL_000a:  nop            //  IL_000b:  ldloc.0            //  IL_000c:  box        [System.Drawing]System.Drawing.Point            //  IL_0011:  call       void [mscorlib]System.Console::WriteLine(object)            //  IL_0016:  nop            //  IL_0017:  ldloca.s   p            //  IL_0019:  ldc.i4.2            //  IL_001a:  ldc.i4.2            //  IL_001b:  call       instance void [System.Drawing]System.Drawing.Point::Offset(int32,            //                                                                                  int32)            //  IL_0020:  nop            //  IL_0021:  ldloc.0            //  IL_0022:  box        [System.Drawing]System.Drawing.Point            //  IL_0027:  call       void [mscorlib]System.Console::WriteLine(object)            //  IL_002c:  nop            //  IL_002d:  ldloc.0            //  IL_002e:  box        [System.Drawing]System.Drawing.Point            //  IL_0033:  stloc.1            //  IL_0034:  ldloc.1            //  IL_0035:  call       void [mscorlib]System.Console::WriteLine(object)            //  IL_003a:  nop            //  IL_003b:  ldloc.1            //  IL_003c:  unbox.any  [System.Drawing]System.Drawing.Point            //  IL_0041:  stloc.3            //  IL_0042:  ldloca.s   CS$0$0001            //  IL_0044:  ldc.i4.3            //  IL_0045:  ldc.i4.3            //  IL_0046:  call       instance void [System.Drawing]System.Drawing.Point::Offset(int32,            //                                                                                  int32)            //  IL_004b:  nop            //  IL_004c:  ldloc.1            //  IL_004d:  call       void [mscorlib]System.Console::WriteLine(object)            //  IL_0052:  nop            //  IL_0053:  ldloc.1            //  IL_0054:  unbox.any  [System.Drawing]System.Drawing.Point            //  IL_0059:  stloc.2            //  IL_005a:  br.s       IL_005c            //  IL_005c:  ldloc.2            //  IL_005d:  ret            //} // end of method BoxAndUnBox::RunSample2            #endregion        }

  結論如下:

  1. 因為Point是值類型,所以進行值拷貝,新對象與舊對象沒有任何關系。
  2. Offset方法的原型是Offset(int,int)所以要對引用類型o進行拆箱操作。
  3. 返回值是復制出來的,值類型復制本身,引用類型返回復制后的指針。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 云阳县| 彰化市| 宣化县| 汕头市| 永顺县| 漳浦县| 嘉禾县| 日土县| 定日县| 潼关县| 万全县| 永寿县| 清镇市| 九龙坡区| 襄城县| 忻州市| 三原县| 浑源县| 肥城市| 青铜峡市| 原平市| 西乌珠穆沁旗| 河南省| 哈巴河县| 麻城市| 夹江县| 哈巴河县| 晋城| 贵南县| 法库县| 阿鲁科尔沁旗| 浮梁县| 赣州市| 郓城县| 饶河县| 房山区| 民权县| 六盘水市| 乌兰浩特市| 翁源县| 波密县|