装箱和拆箱

  今天看书,觉得这个例子很有意思,记录一下,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:  }

  对于输出结果,相信不少人如果不熟悉的话还是会弄错的~

image

  判断还是很简单的,只要想对值类型的一个实例引用,该实例就必须装箱.假如方法要求的参数是引用类型,就会发生装箱~简单的分析:

    .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)
    ...
    IL_0017: ldloca.s p //推送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 类型。

  请注意,对类型形参进行隐式转换将以装箱转换的形式执行(如果在运行时它最后从值类型转换到引用类型)

总结:

  1. 只有接口可以从外部改变值类型的字段
  2. 只要想对值类型的一个实例引用,该实例就必须装箱  
posted @ 2011-10-26 12:16  云是风的梦  阅读(201)  评论(0编辑  收藏  举报