本文中大部分示例代码来自于《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 }
结论如下:
SampleThree:
public bool RunSample3() { var p = new MyPoint(1, 1); // Define struct type object o = p; Console.WriteLine(p.ToString()); // Not Box ((IMyPoint) p).Change(2, 2); // When turn to interface it should boxed and copy a instance. Console.WriteLine(p); // Show 1,1 ((IMyPoint) o).Change(2, 2); // 'o' is a reference type, it not boxed. Console.WriteLine(o); // Show 2,2 return ((MyPoint) o).x.Equals(p.x); #region IL Generate Code //.method public hidebysig instance bool RunSample3() cil managed //{ // // 代码大小 115 (0x73) // .maxstack 3 // .locals init ([0] valuetype Demo_CLRVIACSHARP.MyPoint p, // [1] object o, // [2] bool CS$1$0000, // [3] int32 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 Demo_CLRVIACSHARP.MyPoint::.ctor(int32, // int32) // IL_000a: nop // IL_000b: ldloc.0 // IL_000c: box Demo_CLRVIACSHARP.MyPoint // IL_0011: stloc.1 // IL_0012: ldloca.s p // IL_0014: constrained. Demo_CLRVIACSHARP.MyPoint // IL_001a: callvirt instance string [mscorlib]System.Object::ToString() // IL_001f: call void [mscorlib]System.Console::WriteLine(string) // IL_0024: nop // IL_0025: ldloc.0 // IL_0026: box Demo_CLRVIACSHARP.MyPoint // IL_002b: ldc.i4.2 // IL_002c: ldc.i4.2 // IL_002d: callvirt instance void Demo_CLRVIACSHARP.IMyPoint::Change(int32, // int32) // IL_0032: nop // IL_0033: ldloc.0 // IL_0034: box Demo_CLRVIACSHARP.MyPoint // IL_0039: call void [mscorlib]System.Console::WriteLine(object) // IL_003e: nop // IL_003f: ldloc.1 // IL_0040: castclass Demo_CLRVIACSHARP.IMyPoint // IL_0045: ldc.i4.2 // IL_0046: ldc.i4.2 // IL_0047: callvirt instance void Demo_CLRVIACSHARP.IMyPoint::Change(int32, // int32) // IL_004c: nop // IL_004d: ldloc.1 // IL_004e: call void [mscorlib]System.Console::WriteLine(object) // IL_0053: nop // IL_0054: ldloc.1 // IL_0055: unbox.any Demo_CLRVIACSHARP.MyPoint // IL_005a: ldfld int32 Demo_CLRVIACSHARP.MyPoint::x // IL_005f: stloc.3 // IL_0060: ldloca.s CS$0$0001 // IL_0062: ldloca.s p // IL_0064: ldfld int32 Demo_CLRVIACSHARP.MyPoint::x // IL_0069: call instance bool [mscorlib]System.Int32::Equals(int32) // IL_006e: stloc.2 // IL_006f: br.s IL_0071 // IL_0071: ldloc.2 // IL_0072: ret //} // end of method BoxAndUnBox::RunSample3 #endregion }
结论如下:
欢迎各位看官拍砖~~~
示例代码下载