最近编码时遇到了一个问题,先看一个程序:
static void Main ( string[ ] args )
{
MyValueType mv = new MyValueType ( b: 2.3 );
object objMv = mv;
Console.WriteLine ( mv );
Console.WriteLine ( objMv );
/* Step1 */
( (MyValueType) objMv ).Method1 ( 3.4 );
Console.WriteLine ( objMv );
/* Step2 */
( (IMyValueType) mv ).Method1 ( 4.5 );
Console.WriteLine ( mv );
/* Step3 */
( (IMyValueType) objMv ).Method1 ( 5.6 );
Console.WriteLine ( objMv );
Console.ReadKey ( true);
}
struct MyValueType:IMyValueType{
public int a;
public double b;
public bool c;
public MyValueType ( int a=default(int),
double b=default(double),bool c=default(bool)) {
this.a=a;
this.b = b;
this.c = c;
}
public override string ToString ( )
{
return string.Format ( "a={0},b={1},c={2}", a, b,c );
//return base.ToString ( );
}
public void Method1 ( double b = default(double) )
{
this.b = b;
}
}
interface IMyValueType {
void Method1 ( double b = default(double) );
}
问:上述3个步骤的控制台输出是什么?(主要看b的值)
各位先猜猜b的值在3个步骤中输出是什么,然后复制粘贴运行下,看看是不是答对了!答对了的童鞋也请想想为什么会是这样的结果。
下面引出主题
Struct(值类型)的装箱和拆箱
一、预备知识
1.1 继承
所有值类型都是sealed的,即不能被继承。struct不能继承其它类型(其实已经继承自ValueType类了),不过可以实现多个接口。
1.2 初始化
struct中的每个Field在其默认构造函数中都初始化为default(xxx)类型的值,C#完全禁止了用户显示定义默认构造器,也不能在声明时对Field进行赋值。如果要自己编写构造函数,则必须对每个Field赋值,不能只对其中一些Field赋值。
1.3 存储
struct和class不同,数据存储在栈上,而不是堆上。
二、与引用类型的转换
2.1 装箱步骤(值类型转换为引用类型)
1) 在堆中分配对应的内存空间
2) 内存复制操作,栈上的数据复制到堆中
3) 更新对象或接口引用,使之指向堆中的位置
装箱操作往往是隐式转换中用到的。
2.2拆箱操作(引用类型转换为值类型)
拆箱会解除对堆中对象的引用。一般地,拆箱完毕后会有复制内存的操作。拆箱操作必须显式转换。另外,必须拆箱为其基础类型,见下面代码:
int ia;
object ob;
double dc;
ia=23;
ob=ia;(装箱操作)
/*InvalidCastException,拆箱操作只能返回int类型,应确保源类型能转换成目标类型*/
//dc=(double)ob;
dc=(double)(int)ob;//正确,要首先拆箱为基础类型
三、开头代码的解释
第一步以前:
object objMv = mv;
该步骤引发装箱操作,会在堆上产生一个mv的内存副本,然后objMv指向该副本
第一步:
objMv要调用struct的Method1函数,就必须拆箱为struct。这样,就会产生一个栈上的临时副本,函数Method1对临时副本进行了更改,但是不会被保存在堆上的objMv中。所以堆上的objMv的b值不会改变
第二步:
mv要调用接口IMyValueType的函数Method1,就会被装箱为IMyValueType。这样,就会产生一个堆上的临时副本,函数Method1对临时副本进行了更改,但是不会影响栈上mv的内存数据。
第三步:
引用类型之间的转换,没有装拆箱操作,不会有“复制”操作,即Method1可以更改objMv中的值
四、总结
装箱、拆箱有一些容易被人忽视的特性。就像开头代码一样,以为修改了struct或object的值,其实根本没有修改,从而引发致命错误。记住“不要创建可变值类型”,就会在很大程度上避免其中的多数问题。
Ps:有什么不足之处多多指教哈,谢谢~J