深入浅出boxing和unboxing

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

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

public   interface  IStudent 

String Name

get;set;}
 
}
 
public   struct  Student:IStudent 

public String name; 
public Student(string _name) 
{name=_name;} 
public String Name 

get{return name;} 
set{name=value;} 
}
 
}
 
现在这样调用它: 
Student stu
= new  Student( " axiang " ); 
Object o
= (Object)stu; // boxing 
Student stu1 = (Student)o; // unboxing 
stu1.Name = " andy "
stu
= (Student)o; 
Console.WriteLine(stu.name);
// what would be written? 
IStudent istu = (IStudent)o; // unboxing 
istu.Name = " andychan "
stu
= (Student)o; 
Console.Write(stu.name);
// what would be written? now 

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

好了,这回又来到值类型数据与引用数据类型的问题上来了。上面的例子是用值类型数据为例的,引用类型会是什么结果呢?在MSDN上没有找到相关的帮助,但我们可以试试:
        Class1 m_obj1     =   new  Class1();
        m_obj1.m_member    
=   1 ;
        Console.WriteLine(
" m_obj1.m_member:{0} " ,m_obj1.m_member);
        Object m_obj2    
=  (Object)m_obj1;
        m_obj1.m_member    
=   2 ;
        Console.WriteLine(
" m_obj1.m_member:{0} " ,m_obj1.m_member);
        Console.WriteLine(
" m_obj2.m_member:{0} " ,((Class1)m_obj2).m_member);
        Class1 m_obj3    
=  (Class1)m_obj2;
        m_obj3.m_member    
=   3 ;
        Console.WriteLine(
" m_obj1.m_member:{0} " ,m_obj1.m_member);
        Console.WriteLine(
" m_obj3.m_member:{0} " ,m_obj3.m_member);
类:
class  Class1
{
    
public int m_member    = -1;
}
这回的结果是:
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了,都看的出来,只是引用的赋值。然而这里拿它出来讨论是因为可能会有文章一开始提出的那个问题:就是如果一个结构(值类型数据),如果它实现了一个接口,那么在与接口的转化中,会是什么问题呢?
看这样的例子:
        struct1 m_stru         =   new  struct1();
        m_stru.m_member        
=   1 ;
        Object m_obj        
=  m_stru;
        Iinterface1 m_inter 
=  (Iinterface1)m_obj;
        m_inter.m_ID        
=   3 ;
        struct1 m_stru2        
=  (struct1)m_obj;
        Console.WriteLine(
" m_stru.m_member:{0} " ,m_stru.m_member);
        Console.WriteLine(
" m_stru2.m_member:{0} " ,m_stru2.m_member);
结构与接口:
interface  Iinterface1
{
    
int m_ID{set;get;}
}


struct  struct1:Iinterface1
{
    
public int m_member;
    
Iinterface1
}
输出结果:
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的数据。

你可能感兴趣的:(in)