.net中装箱的几种情况
本篇所写都是本人想当然的理解。如果这种理解便于帮助你理解一些知识的话,我会感到由衷的欣慰。
   .net中虽然没有指针语法,但是在堆中分配对象,将引用放在栈中,十分类似C++中的指针操作,此时引用就可以看成一种特殊的指针。因为指针操作的间接性,会带来一定的性能影响,为了避免这种影响,.net采取了一种折衷的办法,引入了值类型。
   为了在值类型和引用类型之间进行一些合理的转换,于是带来了装箱和拆箱。
   装箱简单来说就是将值类型转换为引用类型。按三步进行: 
(1)新分配托管堆内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)。 
(2)将值类型的实例字段拷贝到新分配的内存中。 
(3)返回托管堆中新分配对象的地址。
    拆箱就是将引用类型转换为对应的值类型。分如下步骤进行:
(1)检查引用对象实例,确保它是给定值类型的一个装箱值。
(2)获取引用对象中指向值类型部分的指针。
(3)将引用对象中对应的内容拷贝到值类型区域。 
从上面步骤可以看出,装箱和拆箱会给程序的性能带来一定的影响,所以我们应尽可能地避免装箱和拆箱。装箱可以隐式进行,拆箱只能显式进行。只有先装箱,才能拆箱。
为了尽可能地避免装箱和拆箱,我们需要了解装箱的几种情况。
我总结了以下几种(可能不太全面):
(1)方法中参数为Object类,但是传递一个值类型。
 void f(object obj)
void f(object obj)2
 {
{3
 
   
4
 }
}5

6
 static void Main(string[] args)
static void Main(string[] args)7
 {
{8
 f(32);
   f(32);9
 }
}
(2)一个类型中有field申明为Object类,赋予一个值类型。
 class Container
class Container2
 {
    {3
 private object m_obj;
        private object m_obj;4

5
 public object Obj
        public object Obj6
 {
        {7
 get { return m_obj; }
            get { return m_obj; }8
 set { m_obj = value; }
            set { m_obj = value; }9
 }
        }10
 
        
11
 }
    }12
 
    13

14
 class Program
    class Program15
 {
    {16
 static void Main(string[] args)
        static void Main(string[] args)17
 {
        {18
 Container con;
            Container con;19
 //这里会发生装箱
            //这里会发生装箱20
 con.Obj = 45;
            con.Obj = 45;21
 }
        }22
 }
    }
(3)调用Object类中没有被值类型覆盖的方法,如GetType()。
 
 2
 //值类型,这里借用《.net框架程序设计》中的例子,并做了适当修改
  //值类型,这里借用《.net框架程序设计》中的例子,并做了适当修改3
 struct MyValType
    struct MyValType4
 {
    {5
 RefType refobj;     //引用类型
        RefType refobj;     //引用类型6
 ValType valobj;     //值类型
        ValType valobj;     //值类型7

8
 public override bool  Equals(object obj)
        public override bool  Equals(object obj)9
 {
        {10
 //这里如果这样写,this.GetType(),会将this装箱。
            //这里如果这样写,this.GetType(),会将this装箱。11
 //因为MyValType没有覆写GetType()方法,会实际使用Object的GetType()方法
            //因为MyValType没有覆写GetType()方法,会实际使用Object的GetType()方法12
 //如果要使用GetType()方法,必须先构建方法表,于是发生装箱
            //如果要使用GetType()方法,必须先构建方法表,于是发生装箱13
 if (this.GetType() != obj.GetType())
            if (this.GetType() != obj.GetType()) 14
 return false;
                return false;15
 return this.Equals((MyValType)obj);
             return this.Equals((MyValType)obj);16
 }
        }17

18
 public Boolean Equals(MyValType obj)
        public Boolean Equals(MyValType obj)19
 {
        {20
 if (!Object.Equals(this.refobj,obj.refobj))
            if (!Object.Equals(this.refobj,obj.refobj))21
 return false;
                return false;22
 if (!this.valobj.Equals(obj.valobj))
            if (!this.valobj.Equals(obj.valobj))23
 return false;
                return false;24
 return true;
            return true;25
 }
        }26
 }
    }
(4)将值类型转换为成一个被该值类型实现的接口类型。
 interface IChange
interface IChange2
 {
    {3
 void Change(System.Int32 x);
        void Change(System.Int32 x);4
 }
    }5

6
 struct MyValType: IChange
    struct MyValType: IChange7
 {
    {8
 private int value;
        private int value;9

10
 public int Value
        public int Value11
 {
        {12
 get { return this.value; }
          get { return this.value; }13
 set { this.value = value; }
          set { this.value = value; }14
 }
        }15

16
 public void Change(System.Int32 x)
        public void Change(System.Int32 x)17
 {
        {18
 value = x;
            value = x;19
 }
        }20
 }
    }21

22
 class Program
    class Program23
 {
    {24
 static void Main(string[] args)
        static void Main(string[] args)25
 {
        {26
 MyValType valType = new MyValType();
            MyValType valType = new MyValType();27
 valType.Value = 10;
            valType.Value = 10;28
 //此时会发生装箱
            //此时会发生装箱29
 IChange iChange = valType;
            IChange iChange = valType;30
 //此时修改,是修改堆中的内存,不会修改valType
            //此时修改,是修改堆中的内存,不会修改valType31
 iChange.Change(20);
            iChange.Change(20);32

33
 //拆箱
            //拆箱34
 MyValType valType2 = (MyValType)iChange;
            MyValType valType2 = (MyValType)iChange;35

36
 //输出10,valType.Value在iChange.Change(20)时不会改变
            //输出10,valType.Value在iChange.Change(20)时不会改变37
 System.Console.WriteLine(valType.Value);
            System.Console.WriteLine(valType.Value);38
 //valType2.Value为20
            //valType2.Value为2039
 System.Console.WriteLine(valType2.Value);
            System.Console.WriteLine(valType2.Value);40
 System.Console.Read();
            System.Console.Read();41
 }
        }42
 }
    }针对以上四种情况,为了减少装箱和拆箱,建议以如下形式进行:
(1)方法中参数为Object类,但是传递一个值类型。               建议利用方法重载或者泛型。
(2)一个类型中有field申明为Object类,赋予一个值类型。   建议利用泛型。
(3)调用Object类中没有被值类型覆盖的方法,如GetType()。
根据实际情况,判断是否有其它方法实现,如上面举的例子就可以这样修改:
 //值类型
//值类型2
 struct MyValType
    struct MyValType3
 {
    {4
 RefType refobj;     //引用类型
        RefType refobj;     //引用类型5
 ValType valobj;     //值类型
        ValType valobj;     //值类型6

7
 public override bool  Equals(object obj)
        public override bool  Equals(object obj)8
 {
        {9
 //这里不用GetType(),可以避免装箱
            //这里不用GetType(),可以避免装箱10
 //同时,因为值类型不能有子类,所以这里用is就可以达到类型比较的目的
        //同时,因为值类型不能有子类,所以这里用is就可以达到类型比较的目的11
 if (!(obj is MyValType))
            if (!(obj is MyValType))12
 return false;
                return false;13
 return this.Equals((MyValType)obj);
         return this.Equals((MyValType)obj);14
 }
        }15

16
 public Boolean Equals(MyValType obj)
        public Boolean Equals(MyValType obj)17
 {
        {18
 if (!Object.Equals(this.refobj,obj.refobj))
            if (!Object.Equals(this.refobj,obj.refobj))19
 return false;
                return false;20
 if (!this.valobj.Equals(obj.valobj))
            if (!this.valobj.Equals(obj.valobj))21
 return false;
                return false;22
 return true;
            return true;23
 }
        }24
 }
    }
(4)将值类型转换为成一个被该值类型实现的接口类型。如果设计上真要求这么做,那可能只能如此了。我暂时没有想到什么解法,如果你有更好的解法,希望不吝赐教。
 
                    
                
 
         
                
            
         
 浙公网安备 33010602011771号
浙公网安备 33010602011771号