今天稍有点空闲,本来想看看网页啥的,无奈老板坐镇,只好翻开《CLR VIA C#》
有些地方有点迷糊,准备敲代码试一下,打开学习用的项目,突然发现有个以前看《深入C#内存管理来分析值类型&引用类型,装箱&拆箱,堆栈几个概念组合之间的区别》遗留下来的问题
代码是这样子的
1 int i = 4;
2 object o = i;
3 object o2 = o;
4 Console.WriteLine(ReferenceEquals(o, o2)); //true
5 o = 8;
6 Console.WriteLine(ReferenceEquals(o, o2)); //false
7
8 Console.WriteLine("i={0}, o={1}, o2={2}", i, o, o2); //4, 8, 4, why?
2 object o = i;
3 object o2 = o;
4 Console.WriteLine(ReferenceEquals(o, o2)); //true
5 o = 8;
6 Console.WriteLine(ReferenceEquals(o, o2)); //false
7
8 Console.WriteLine("i={0}, o={1}, o2={2}", i, o, o2); //4, 8, 4, why?
结果原帖子里也有,但是为什么,不太明白,只是知道和装箱有点关系(int转成object当然要装箱)。查引用,上面代码第4行,说明两个指向的是同一个对象,但o=8之后,两个就不是同一个对象了。这是为啥?
无奈祭出ILDASM,这回现形了(略去Console.WriteLine之类的代码)
1 IL_0001: ldc.i4.4
2 IL_0002: stloc.0
3 IL_0003: ldloc.0
4 IL_0004: box [mscorlib]System.Int32
5 IL_0009: stloc.1
6 IL_000a: ldloc.1
7 IL_000b: stloc.2
8 IL_000c: ldc.i4.8
9 IL_000d: box [mscorlib]System.Int32
10 IL_0012: stloc.1
2 IL_0002: stloc.0
3 IL_0003: ldloc.0
4 IL_0004: box [mscorlib]System.Int32
5 IL_0009: stloc.1
6 IL_000a: ldloc.1
7 IL_000b: stloc.2
8 IL_000c: ldc.i4.8
9 IL_000d: box [mscorlib]System.Int32
10 IL_0012: stloc.1
1. 创建一个常数,值为4,入栈
2. 出栈,将值放入第一个内存变量(int i = 4)
3. 将第一个内存变量入栈
4. 装箱(object o = i)
5. 出栈,将o的地址放入第二个内存变量
6. 将第二个内存变量入栈
7. 出栈,将地址放入第三个内存变量(o的地址)(object o2 = o)
8. 创建一个常数,值为8,入栈
9. 装箱
10. 出栈,将装箱后的地址放入第二个内存变量(o = 8)
这样看来就比较清楚了,o2和o的地址明显不一样,因为o=8中有一个对8的装箱操作,之后仅修改了第二个内存变量中保存的地址,而第三个内存变量中保存的仍然是上次装箱4时的地址,所以o=8, o2=4
顺便说一下string吧
string是不可变的,也就是说,一个string对象创建以后,如果修改它的内容,就会创建一个新的string对象,换句话说,地址变了。《CLR VIA C#》中的解释是string pooling,所有内容相同的字符串对象都会指向同一个metadata中的同一个string对象。所以下面的这段代码
1 String s1 = "Hello";
2 String s2 = "Hello";
3 Console.WriteLine(Object.ReferenceEquals(s1, s2));
2 String s2 = "Hello";
3 Console.WriteLine(Object.ReferenceEquals(s1, s2));
结果是True