1 Struct A : ICloneable 2 { 3 public Int32 x; 4 public override String ToString() { 5 return String.Format("{0}",x); 6 } 7 public object Clone() { 8 return MemberwiseClone(); 9 } 10 } 11 static void main() 12 { 13 A a; 14 a.x = 100; 15 Console.WriteLine(a.ToString()); 16 Console.WriteLine(a.GetType()); 17 A a2 = (A)a.Clone(); 18 ICloneable c = a2; 19 Ojbect o = c.Clone(); 20 } 21 5.0:a.ToString()。编译器发现A重写了ToString方法,会直接调用ToString的指令。因为A是值类型,编译器不会出现多态行为。
因此,直接调用,不装箱。(注:ToString是A的基类System.ValueType的方法)
上文是重写了ToString的方法,不装箱。但是,如果不重写ToString的方法,当值类型以字符串形式出现的时候,会不会装箱呢~?
那就会调用valueType的ToString()
http://msdn.microsoft.com/zh-cn/library/vstudio/system.valuetype.aspx
调用ValueType的ToString,有些情况还是会进行装箱的
例如,值类型的ToString方法在内部调用base.ToString()
@kk_liang:
呵呵 只要 ValueType 的 ToString没有调用 base.ToString() 就行了啊
现在大部分所说的装箱都有一个误解就是转换成引用类型,
其实 装箱 是 值类型要转换成 Object 或者是该值类型继承的 接口类型。参考MSDN
一般 就是看 有没有和object发生类型转换,理解这点,就好办了。
你例子中 的 a.ToString是 a 本身的 ToString,不发生类型转换,当然不装箱。
如果A 不重写ToString,a.ToString() 发生了类型转换,就是转换成了ValueType,再调用的ToString。并没有转换成object,当然不发生装箱。
我不同意你的看法...
1,《CLR Via C#》里面说到,为了将一个值类型转换成一个引用类型,要使用一个名为装箱的机制。
这里说到的,可以这么理解--值类型装换成引用类型,就是一个装箱的过程,对吧~
2, 装箱 是 值类型要转换成 Object 或者是该值类型继承的 接口类型。
按照上面所说的,根据用户自定义的类型,再进行相互装换的话,还是会进行装箱的
例子源用MSDN的http://msdn.microsoft.com/zh-cn/library/xhbhezf4.aspx
然后我再看了例子的IL代码,结果如下
1 .method private hidebysig static void Main() cil managed 2 { 3 .entrypoint 4 // 代码大小 99 (0x63) 5 .maxstack 2 6 .locals init ([0] class ConsoleApplication2.Fahrenheit fahr, 7 [1] class ConsoleApplication2.Celsius c, 8 [2] class ConsoleApplication2.Fahrenheit fahr2) 9 IL_0000: nop 10 IL_0001: ldc.r4 100. 11 IL_0006: newobj instance void ConsoleApplication2.Fahrenheit::.ctor(float32) 12 IL_000b: stloc.0 13 IL_000c: ldstr "{0} Fahrenheit" 14 IL_0011: ldloc.0 15 IL_0012: callvirt instance float32 ConsoleApplication2.Fahrenheit::get_Degrees() 16 IL_0017: box [mscorlib]System.Single 17 IL_001c: call void [mscorlib]System.Console::Write(string, 18 object) 19 IL_0021: nop 20 IL_0022: ldloc.0 21 IL_0023: call class ConsoleApplication2.Celsius ConsoleApplication2.Fahrenheit::op_Explicit(class ConsoleApplication2.Fahrenheit) 22 IL_0028: stloc.1 23 IL_0029: ldstr " = {0} Celsius" 24 IL_002e: ldloc.1 25 IL_002f: callvirt instance float32 ConsoleApplication2.Celsius::get_Degrees() 26 IL_0034: box [mscorlib]System.Single 27 IL_0039: call void [mscorlib]System.Console::Write(string, 28 object) 29 IL_003e: nop 30 IL_003f: ldloc.1 31 IL_0040: call class ConsoleApplication2.Fahrenheit ConsoleApplication2.Celsius::op_Explicit(class ConsoleApplication2.Celsius) 32 IL_0045: stloc.2 33 IL_0046: ldstr " = {0} Fahrenheit" 34 IL_004b: ldloc.2 35 IL_004c: callvirt instance float32 ConsoleApplication2.Fahrenheit::get_Degrees() 36 IL_0051: box [mscorlib]System.Single 37 IL_0056: call void [mscorlib]System.Console::WriteLine(string, 38 object) 39 IL_005b: nop 40 IL_005c: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() 41 IL_0061: pop 42 IL_0062: ret 43 } // end of method MainClass::Main
第16行和26行说明还是会进行装箱操作的,而且, Celsius和Fahrenheit这两个类不是相互类型所继承的接口类型...
以上均是给人观点,请猛拍
@Qlin 大哥,第2点我说错了,那是WriteLine方法参数转为Object的时候装箱的,只是调用各自类中的方法而已...
@kk_liang:
大哥 Il代码看着头痛,不太懂 呵呵,你的 16 行 和 26行是不是 创建对象时,值类型保存到 托管堆中。
Celsius和Fahrenheit 两个都是引用类型,都是在托管堆中,互相转换,谈不上 装箱,甚至不会申请新的内存。
@Qlin: 16行和26行是调用 console.WriteLine(string,object)的时候,参数如你所说的转换Object类型是发生装箱的,所以是我搞错了,不好意思啊~
基本现在理清一下思路就是,Int.ToString()是不需要装箱的,会调用ValueType的ToString方法...
@kk_liang: Int.ToString不需要装箱是因为它本身正确地实现了ToString方法,而且并不是调用的ValueType.ToString,调用ValueType.ToString会导致装箱。
@水牛刀刀: 您这么说我又搞不懂了~本身实现了ToString方法,那也就是从值类型转换成引用类型对吧~为什么不装箱呢~?
不会,CLR Via C# 建议用 ToString() 的
看我的答案。
如果你的值类型自己override了ToString方法,那么不会装箱。否则,会调用ValueType.ToString方法,由于ValueType是类,所以要装箱。你可以去看生成的IL里是否有box指令。
我试过了,不会装箱...
@kk_liang: 我刚看了下,没有box指令,但是有constrained指令,这个指令的作用详细看这里, 重点部分我贴出来:
If thisType is a reference type (as opposed to a value type) then ptr is dereferenced and passed as the 'this' pointer to the callvirt of method.
If thisType is a value type and thisType implements method then ptr is passed unmodified as the 'this' pointer to a call method instruction, for the implementation of methodby thisType.
If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the 'this' pointer to the callvirt method instruction.
这里是最后一种情况,因此会装箱。以上可能太理论,不过我建议以后碰到类似问题你可以自己实践一下:比如创建2个struct A和B,一个实现ToString另外一个不实现,然后调用他们的ToString方法各100000次,比较一下时间,就很容易得出结论(我已经试过了,证实了我的说法是对的)。注意测试的时候应当是RELEASE编译,并且注意JIT的影响。
@水牛刀刀: 已学习,谢谢指教。待会去测试下看看。三克油啊。
@水牛刀刀: 我试过了下,没有出现constrained指令?为啥?
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 12 (0xc)
.maxstack 1
.locals init ([0] int32 i,
[1] string s)
IL_0000: nop
IL_0001: ldc.i4.5
IL_0002: stloc.0
IL_0003: ldloca.s i
IL_0005: call instance string [mscorlib]System.Int32::ToString()
IL_000a: stloc.1
IL_000b: ret
} // end of method Program::Main
@kk_liang: 你用Int32做实验?Int32自己实现了ToString方法,当然不会装箱。你问题里不是一个自定义的struct么。
@水牛刀刀: 嗯~是的,这次真的搞懂了什么回事了~...
昨天的博文不是说这个问题的,就是说Int32.ToString()的,我一个心急把它给删了~呵呵~...
谢谢你的耐心回答...
不会的!
看我的答案。