深入浅出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的数据。
原文地址:https://www.cnblogs.com/WuCountry/p/341296.html