本文中大部分示例代碼來自于《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操作都會對程序的性能產生不利的影響,我們要避免此類問題的發生。
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中我們得到如下結論:
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 }結論如下:
新聞熱點
疑難解答