zoukankan      html  css  js  c++  java
  • 值类型的装箱和拆箱

    值类型是比引用类型更“轻型”的一种类型,因为它不需要作为对象在托管堆中分配,不会被垃圾回收,也不通过指针来引用。

            static void Main()
    {
    System.Collections.ArrayList list = new System.Collections.ArrayList();
    Point p;//分配一个Point,不在堆中分配。
    for (int i = 0; i < 20; i++)
    {
    p.x = p.y = i;//初始化值类型的成员
    list.Add(p);//对值类型进行装箱,并将引用添加到ArrayList中。C#编译器会自动的生成对一个值类型的实例进行装箱所需的IL代码,C#编译器检测到是向一个需要引用类型的方法传递一个值类型,所以会自动生成代码对对象进行装箱。在运行时,当前存在于Point值类型实例p中的字段会复制到新分配的Point对象中。已装箱的Point对象(已经是引用类型)的地址会返回给Add方法。Point对象会一直存在于堆中,直到被垃圾回收。Point值类型的变量p可以重用,因为ArrayList根本不知道关于它的任何事情。
               Point p1 = (Point)list[0];//拆箱  ,   
    }
    }

    List.Add()方法需要一个object参数,Add需要获取对托管堆上的一个对象的引用(或指针)来作为参数。在代码中传递的是p,是一个值类型,为了使代码正确工作,Point已经进行了一次装箱。

    对一个值类型的实例进行装箱操作时发生在内部的事情:

    1.在托管堆中分配内存。

    2.值类型的字段复制到新分配的堆内存中。

    3.返回对象的地址,现在这个地址是对一个对象的引用,值类型现在是一个引用类型。

    拆箱发生的事:

    1.获取已装箱的对象的各个字段的地址。

    2.将字段包含的值从堆中复制到基于栈的值类型实例中。

    拆箱的代价比装箱的低得多。拆箱就是获取一个指针的过程,该指针包含在一个对象中的原始值类型,事实上,指针指向的是已装箱实例中未装箱的部分,所以和装箱不同,拆箱不要求在内存中复制任何字节,往往会紧接着拆箱操作发生一次字段的复制操作。
     

    装箱和拆箱会对应用程序的速度和内存消耗产生不利影响,所以应该注意编译器在什么时候生成代码来自动这些操作,并尝试手动编写代码,尽量避免自动生成代码的情况。

            static void Main()
    {
    Int32 i = 5;
    object obj = i;//装箱
    Int16 n = (Int16)obj;//拆箱失败,抛出InvalidCastException异常。如果引用指向的对象不是所期待的值类型的一个已装箱实例,就抛出此异常。
    Int16 nn = (Int16)(Int32)obj;//先拆箱为正确的类型,在进行转型。
    Int32 d = (Int32)obj;//
    Console.ReadKey();
    }

    在对一个对象进行拆箱时,只能将其转型为原先未装箱时的值类型。 

            static void Main()
    {
    Int32 i = 5;
    object obj = i;//装箱
    i = 123;
    Console.WriteLine(i + "," + (Int32)obj);//发生了多少次装箱?WriteLine()需要获取一个String对象,但是当前没有String对象,所以采取
    //某种方式对这些数据进行合并,创建一个String对象。
    //为了创建String对象编译器生成代码来调用String对象的静态方法Concat。
    //String.Concat方法有几个重载版本,所有版本的执行操作都是一样的,不同的是参数数量。
    //这里连接3个数据项创建一个字符串,所以编译器选择的是
    //public static String Concat(object obj0,object obj1,object obj2) 这个版本
    //为第一个参数传递的是i,i是一个未装箱的值类型,装箱。
    //obj1是一个String对象的引用。obj3要求一次拆箱操作。然后装箱。
    Console.WriteLine(i.ToString() + "," + obj);//这里没有装箱操作。i调用ToString方法,返回一个String,String对象已经是引用类型,所以能直接传递给Concat方法,不需要任何装箱操作
    Console.ReadKey();
    }  


    看看FCL,会发现许多方法都针对不同的值类型参数进行重载。大多数方法进行重载的唯一目的就是减少常值类型的装箱操作次数。

    未装箱的值类型比引用类型更“轻型”,归结于以下两个原因:

    1.值类型不再托管堆上。

    2.值类型没有堆上的每个对象都有的额外成员:一个类型对象指针和同步块索引。

    因为值类型没有同步块索引,所以哈,不能使用System.Threading.monitor类型的各种方法,实现不了同步。

    在C#中只用使用接口才能修改一个已装箱的值类型中的字段,详情见126页。

  • 相关阅读:
    CentOS 7 修改国内yum源
    k8s 安装
    python2 python3同时安装了scrapy如何区分调用
    scrapy log 设置
    hello django
    linux 分割大文件
    scrapy 对不同的Item进行分开存储
    纯C实现的一套low b 贪吃蛇(娱乐版)
    Python之如何实现一行输入多个值
    HDU2571:命运(DP)
  • 原文地址:https://www.cnblogs.com/smailxiaobai/p/2260403.html
Copyright © 2011-2022 走看看