深入浅出boxing和unboxing

上次写的一篇关于ref和out关键字的问题,算是自我反省了一次。不过我觉得学习就应该有一种不怕挫折的的精神。继续学习,继续写文章。

这次讨论一下boxing和unboxing问题,这是我今天在一论坛上看到的问题(源文代码):

None.gif public   interface  IStudent 
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
ExpandedSubBlockStart.gifContractedSubBlock.gifString Name
dot.gif
ExpandedSubBlockEnd.gif
get;set;}
 
ExpandedBlockEnd.gif}
 
None.gif
public   struct  Student:IStudent 
ExpandedBlockStart.gifContractedBlock.gif
dot.gif
InBlock.gif
public String name; 
InBlock.gif
public Student(string _name) 
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{name=_name;} 
InBlock.gif
public String Name 
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif
getdot.gif{return name;} 
ExpandedSubBlockStart.gifContractedSubBlock.gif
setdot.gif{name=value;} 
ExpandedSubBlockEnd.gif}
 
ExpandedBlockEnd.gif}
 
None.gif现在这样调用它: 
None.gifStudent stu
= new  Student( " axiang " ); 
None.gifObject o
= (Object)stu; // boxing 
None.gif
Student stu1 = (Student)o; // unboxing 
None.gif
stu1.Name = " andy "
None.gifstu
= (Student)o; 
None.gifConsole.WriteLine(stu.name);
// what would be written? 
None.gif
IStudent istu = (IStudent)o; // unboxing 
None.gif
istu.Name = " andychan "
None.gifstu
= (Student)o; 
None.gifConsole.Write(stu.name);
// what would be written? now 

我先不讨论这个结果是什么,显然它这里是用到了boxing与unboxing的问题。我们先从最简单的boxing与unboxing来分析:
None.gif          int  m_int1         =   1 ;
None.gif        Object m_obj1    
=  m_int1;         // boxing
None.gif
         int  m_odb2         =  ( int )m_obj1;     // unboxing
None.gif
        m_odb2             =   2 ;
None.gif        Console.WriteLine(
" m_int1:{0} " ,m_int1);
None.gif        Console.WriteLine(
" m_odb2:{0} " ,m_odb2);
显示结果为
m_int1:1
m_odb2:2
看一下MSDN里的帮助:找到了这样的一张图来说明问题:
untitled.bmp
可以清楚的看到,在boxing与unboxing的时候,分别进行了两次内存COPY(而在Jeffery先生的书上说明只有一次,而在unboxing的时候是有一次内存COPY的)。

好了,这回又来到值类型数据与引用数据类型的问题上来了。上面的例子是用值类型数据为例的,引用类型会是什么结果呢?在MSDN上没有找到相关的帮助,但我们可以试试:
None.gif         Class1 m_obj1     =   new  Class1();
None.gif        m_obj1.m_member    
=   1 ;
None.gif        Console.WriteLine(
" m_obj1.m_member:{0} " ,m_obj1.m_member);
None.gif        Object m_obj2    
=  (Object)m_obj1;
None.gif        m_obj1.m_member    
=   2 ;
None.gif        Console.WriteLine(
" m_obj1.m_member:{0} " ,m_obj1.m_member);
None.gif        Console.WriteLine(
" m_obj2.m_member:{0} " ,((Class1)m_obj2).m_member);
None.gif        Class1 m_obj3    
=  (Class1)m_obj2;
None.gif        m_obj3.m_member    
=   3 ;
None.gif        Console.WriteLine(
" m_obj1.m_member:{0} " ,m_obj1.m_member);
None.gif        Console.WriteLine(
" m_obj3.m_member:{0} " ,m_obj3.m_member);
类:
None.gif class  Class1
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
public int m_member    = -1;
ExpandedBlockEnd.gif}
这回的结果是:
m_obj1.m_member:1
m_obj1.m_member:2
m_obj2.m_member:2
m_obj1.m_member:3
m_obj3.m_member:3
也就是说,boxing与unboxing在引用类型数据上没有起到作用!正如 深入剖析引用参数Ref和Out中所讨论到的,这里在堆上的只是引用类型数据的值,而好象是boxing与unboxing的地方,只是做的引用COPY,其实根本上算不上了boxing与unboxing了,都看的出来,只是引用的赋值。然而这里拿它出来讨论是因为可能会有文章一开始提出的那个问题:就是如果一个结构(值类型数据),如果它实现了一个接口,那么在与接口的转化中,会是什么问题呢?
看这样的例子:
None.gif         struct1 m_stru         =   new  struct1();
None.gif        m_stru.m_member        
=   1 ;
None.gif        Object m_obj        
=  m_stru;
None.gif        Iinterface1 m_inter 
=  (Iinterface1)m_obj;
None.gif        m_inter.m_ID        
=   3 ;
None.gif        struct1 m_stru2        
=  (struct1)m_obj;
None.gif        Console.WriteLine(
" m_stru.m_member:{0} " ,m_stru.m_member);
None.gif        Console.WriteLine(
" m_stru2.m_member:{0} " ,m_stru2.m_member);
结构与接口:
None.gif interface  Iinterface1
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
ExpandedSubBlockStart.gifContractedSubBlock.gif    
int m_IDdot.gif{set;get;}
ExpandedBlockEnd.gif}

None.gif
None.gif
struct  struct1:Iinterface1
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
public int m_member;
ContractedSubBlock.gifExpandedSubBlockStart.gif    
Iinterface1#region Iinterface1
InBlock.gif    
public int m_ID
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
ExpandedSubBlockStart.gifContractedSubBlock.gif        
setdot.gif{this.m_member=value;}
ExpandedSubBlockStart.gifContractedSubBlock.gif        
getdot.gif{return this.m_member;}
ExpandedSubBlockEnd.gif    }

ExpandedSubBlockEnd.gif    
#endregion

ExpandedBlockEnd.gif}
输出结果:
m_stru.m_member:1
m_stru2.m_member:3
看清楚了吗?即:用接口来修改了值类型(结构)里的数据!

其实这里已经不能说m_obj是值类型数据了,因为经过boxing,它已经成了引用类型。但由于它是用struct通过boxing过去的,所以再以struct身份unboxing回来的时候,都将会产生值类型unboxing的效果,也就是从栈上COPY一份数据到堆上,最终就是所有的数据修改都不会影响boxing后的引用数据。但是,如果用接口(interface)来处理的时候,就不会COPY内存,因为接口也是引用类型,这样在object与interface之间转化的时候,可以不COPY内存。我们就可以像上面的例子那样,用接口来来处理经过boxing的"值类型"数据。

其实这一问题在Jeffery的书中已经讨论过,这就是一种他所说的内存“欺骗”。这样的方法听说在CV++中经常用,而在C#里,本来是不用的,但这样的结构在编译时“骗过”了编译器,而且取得了在程序设计上的灵活性。然而Jeffery先生并不赞成这样的使用方法,因为虽然取得了一定的灵活性,同时也程序变得复杂了。

至于本文一开始的那段代码的结果,应该可以分析出来了,就是用struct来转时候,是一次内存的COPY,也就是unboxing,而用interface来转的时候,就只是一次引用的COPY,所以通过interface可以修改到boxing后的struct的数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值