值类型的装箱和拆箱--深度解析

对于值类型的装箱和拆箱,《CLR Via c#》一书中用了大量页数来讲,足以表明其重要性。

CTS有两种类型:值类型和引用类型。两种类型的互相转换通过装箱和拆箱完成。
装箱:将值类型转换成引用类型
拆箱:将引用类型转换成值类型

值类型存在两种形式:未装箱和已装箱
装箱内部实现过程:
1、在托管堆中分配内存。分配的内存量时值类型各字段所需的内存量,加上托管堆所有对象都会有的两个额外成员(类型对象指针和同步块索引)所需的内存量。
2、值类型的字段复制到新分配的堆内存中。
3、返回对象地址(引用)。

拆箱内部实现过程:
1、获取已装箱值类型对象中的各个字段的地址 — 这个过程就是拆箱(获取地址)
2、将字段包含的值从堆中赋值到基于栈的值类型实例中。

根据上面的过程可以看出,拆箱的成本要比装箱低很多,毕竟不需要开辟新的内存,且在内存中不复制任何字节(只是获取地址),后续第二步是紧跟拆箱后的一个字段复制操作。

看一段代码

int v = 5;
object o = v;
v = 123;
Console.WriteLine(v + "," + (int)o);   //输出123,5

以上代码发生了3次装箱:
第一次装箱:object o = v;
当进行WtiteLine的时候,是执行了string.Concat方法。

public static string Concat(object arg0,object arg1,object arg2)

可以看到参数的类型是object,所以,打印的第一个v是int类型,需要转成object,这是第二次装箱。
接下来很明显,o转成了int,当然要输出的话,接着还会转成object,这是第三次装箱。

上述代码可以写成这样,只有一次装箱操作:

int v = 5;
object o = v;
v = 123;
Console.WriteLine(v.tostring() + "," + o);   //输出123,5

关键是v.tostring()为什么没有装箱呢?因为值类型重写了Object的ToString()方法,直接返回值类型的字符串格式。所以就没有转object这样的装箱操作。
如果还要问为什么转成字符串不是装箱,首先第一点要理解装箱是什么。其次v.tostring()仅仅是创建了一个字符串而已,跟string str = “5”;一样。

再看一段代码:

int v= 5;
object o = v;
v = 123;
Console.WriteLine(v);  //输出123
v = (int)o;
Console.WriteLine(v);  //输出5

以上代码发生了1次装箱。
因为Console.WriteLine()有重载方法

public static void WriteLine(Int32);  //我们平时写的int其实是Int32的简写,这个在这里不展开了。

所以看是否装箱,要先看一下方法的定义。

特别注意:(下面看一段原书中的话)
如果值类型重写了基类中任何虚方法,那么CLR可非虚的调用该方法,因为值类型隐式密封,不可能有类型从他们派生,而且调用虚方法的值类型实例没有装箱。然而,如果重写的虚方法要调用方法在基类中的实现,那么在调用基类的实现时,值类型实例会装箱,以便能通过this指针将堆一个堆对象的引用传给基方法。
看段代码来理解:

    public struct Point
    {
        int x;
    }

    public struct Point1
    {
        int x;

        public override string ToString()
        {
            return base.ToString();
        }
    }

    public struct Point2
    {
        int x;

        public override string ToString()
        {
            return x.ToString();
        }
    }

    public void Main()
    {
        Point1 p1 = new Point1();
        Point2 p2 = new Point2();

        p1.ToString();  //p1会装箱,因为调用的是ValueType类的ToString()
        p2.ToString();  //p2不会装箱,因为调用的是Int32.ToString()

        Point p3 = new Point();
        p3.ToString();  //p3会装箱,因为调用的是ValueType类的ToString()
        p3.GetType();  //p3会装箱,因为是调用的Object.GetType();
    }

关于装箱最后注意一点:
如果直到自己的代码会造成编译器返回对一个值类型装箱,请改成用手动 方式对值类型进行装箱,这样代码会变得更小,更快。
(何为手动,就是自己写代码先给他装箱,不要让编译器来处理)

未装箱的值类型比已装箱的值类型更“轻”,因为:
1、不在托管堆中分配
2、没有堆上的每个对象都有的额外成员:类型对象指针和同步块索引。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值