装箱和拆箱
今天看书,觉得这个例子很有意思,记录一下,mark~
首先放上例子
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace Boxing {
7: class Program {
8: interface IChange {
9: void Change(int x, int y);
10:
11: }
12: struct Point : IChange {
13: int x, y;
14: public Point(int x, int y) {
15: this.x = x;
16: this.y = y;
17: }
18:
19: public void Change(int x, int y) {
20: this.x = x;
21: this.y = y;
22: }
23:
24: public override string ToString() {
25: return String.Format("({0},{1})", x, y);
26: }
27: }
28: static void Main(string[] args) {
29: Point p = new Point(1, 1);
30:
31: Console.WriteLine(p); //box
32: p.Change(2, 2); //由于已经装箱 由此函数传参是引用类型的值传递了,传地址,改变p
33: Console.WriteLine(p);
34:
35: object o = p; //box
36: p.Change(3, 3);
37: Console.WriteLine(p);
38:
39: ((Point)o).Change(4, 4);//unbox 函数的传参方式为值类型的值传递了,传复制的一个值不改变p
40: Console.WriteLine(p);
41:
42: ((IChange)p).Change(5, 5);//box 更改装箱对象 然后丢弃 准备GC过程
43: Console.WriteLine(p);
44:
45: ((IChange)o).Change(6, 6);//更改装箱对象,这是唯一可以从外部改变值类型的方式
46: Console.WriteLine(o);
47:
48: Console.ReadLine();
49: }
50: }
51: }
对于输出结果,相信不少人如果不熟悉的话还是会弄错的~
判断还是很简单的,只要想对值类型的一个实例引用,该实例就必须装箱.假如方法要求的参数是引用类型,就会发生装箱~简单的分析:
.maxstack 3
.entrypoint
.locals init (
[0] valuetype Boxing.Program/Point p,
[1] object o,
[2] valuetype Boxing.Program/Point CS$0$0000
)
//第一次装箱是因为方法参数要求Object是引用类型
IL_000b: ldloc.0
IL_000c: box Boxing.Program/Point
IL_0011: call void [mscorlib]System.Console::WriteLine(object)
...//调用已经改变了的p
IL_0017: ldloca.s p //推送p的地址到堆栈 这样就可以改变p
IL_0021: ldloc.0
IL_0022: box Boxing.Program/Point
IL_0027: call void [mscorlib]System.Console::WriteLine(object)
IL_002c: nop
IL_002d: ldloc.0
IL_002e: box Boxing.Program/Point
...
IL_003e: ldloc.0
IL_003f: box Boxing.Program/Point
IL_0044: call void [mscorlib]System.Console::WriteLine(object)
...
IL_004a: ldloc.1
IL_004b: unbox.any Boxing.Program/Point
IL_0050: stloc.2
IL_0051: ldloca.s CS$0$0000//拆箱过程p给复制到一临时变量 因此p不受影响
...
IL_005b: ldloc.0
IL_005c: box Boxing.Program/Point
IL_0061: call void [mscorlib]System.Console::WriteLine(object)
...
IL_0067: ldloc.0
IL_0068: box Boxing.Program/Point//类型转为接口类发生box
//方法完成后将该返回值推送到堆栈上 但是没有返回值 因此方法结束后已装箱对象准备垃圾回收
IL_006f: callvirt instance void Boxing.Program/IChange::Change(int32, int32)
...
IL_0075: ldloc.0
IL_0076: box Boxing.Program/Point
IL_007b: call void [mscorlib]System.Console::WriteLine(object)
IL_0080: nop
IL_0081: ldloc.1
IL_0082: castclass Boxing.Program/IChange//这里发生了引用对象转为类
...
IL_008f: ldloc.1
IL_0090: call void [mscorlib]System.Console::WriteLine(object)
由此可知,只有接口可以从外部改变值类型的字段!另外,未装箱的对象调用继承或者重新的虚方法(Tostring,GetType,GetHashCode…)也会发生装箱.常见发生转换的时候:[参见 《C#规范 4.0》]
· 从任何 value-type 到 object 类型。
· 从任何 value-type 到 System.ValueType 类型。
· 从任何 non-nullable-value-type 到 value-type 实现的任何 interface-type。
· 从任何 nullable-type 到由 nullable-type 的基础类型实现的任何 interface-type。
· 从任何 enum-type 到 System.Enum 类型。
· 从任何具有基础 enum-type 的 nullable-type 到 System.Enum 类型。
请注意,对类型形参进行隐式转换将以装箱转换的形式执行(如果在运行时它最后从值类型转换到引用类型)
总结:
- 只有接口可以从外部改变值类型的字段
- 只要想对值类型的一个实例引用,该实例就必须装箱
- 博客是我学习和思考的输出,愿你有所收获。
- 有想法请留言,共同探讨学习。
- 由于博主能力有限,文中可能存在描述不当,恳请指正!
- 你也可以关注我的公众号:ProgramLife042,名称:风之程序人生,方便接收最新内容。