NET中的类型和装箱/拆箱原理

谈到装箱拆箱,DebugLZQ相信给位园子里的博友一定可以娓娓道来,大概的意思就是值类型和引用类型的相互转换呗---值类型到引用类型叫装箱,反之则叫拆箱。这当然没有问题,可是你只知道这么多,那么DebugLZQ建议你花点时间看看楼主这篇文章,继续前几篇博文的风格--浅谈杂侃。

  1. .NET中的类型

  为了说明装箱和拆箱,那首先必须先说类型。在.NET中,我们知道System.Object类型是所有内建类型的基类。注意这里说的是内建类型,程序员可以编写不继承子自System.Object的类型,这里不做过多的介绍(感兴趣的博友可以研究一下)。

  所有.NET的类型都可以分为两类(有点不严谨,但是大家都这么讲):值类型和引用类型。那么值类型和引用类型如何区分,标准是什么?最简单也最明确的一个区分标准是:所有的值类型都继承自System.ValueType(System.ValueType继承自System.Object),也就是说,所有继承自System.ValueType的类型都是值类型,而其他类型都是引用类型。(题外话:以前在读一位博友王涛的《你必须知道的.NET》中,他说,值类型和引用类型最本质的区别是:值类型和引用类型在内存中分配的位置不同,前者分配在堆栈上,后者分配在堆上。个人觉得这个不是一个简单明确的区分方法。远没有DebugLZQ说的这么露骨!)

  说到这里,你应该要有这样的想法:严格来说的话,System.Object作为所有内建类型的基类,本身并没有值类型和引用类型之分。但是System.Object的对象,具有引用类型的特点。这也是值类型在有些场合需要装箱拆箱的原因。

  下面还是简单说下值类型和引用类型的不一样的地方吧,分3块,个人觉得理解这3块就可以了:

  1. 变量赋值   值类型的变量将直接获得一个真实的数据副本,而对引用类型的赋值仅仅是吧对象的引用赋给变量,这样就可能导致多个变量引用到一个实际对象实例上(这里需要各位博友去理解.NET对String的一些优化机制,本质和这个不相悖)。
  2. 内存分配   引用类型的对象将在堆上分配内存,而值类型的对象则会在堆栈上分配内存。(内存如何分配:堆栈上存的是什么?值类型变量和引用类型变量的引用。堆上存的是什么?引用类型的对象(包括了类型对象指针和同步块索引,注意只是个索引,这是.NET为线程同步提出的一种折中的办法。))。大对象堆(也是堆,一种特别的堆)什么的这里不做介绍。但必须说明的是:堆栈的空间有限,但运行效率却比堆要高得多!!!
  3. 由于所有的值类型都继承自System.ValueType,而System.ValueType继承自System.Object,并重新实现了基类System.Object的一个虚方法Equals,而引用类型并没有重写。

  2.装箱拆箱原理

  前面简单介绍了.NET中的类型,下面引入装箱和拆箱。通过1我们知道值类型的对象是在堆栈上分配内存的,而引用类型(包括System.Object)对象是在堆上分配内存的,那么当值类型被类型转换时,会在堆栈和堆上进行一系列的操作,这就是装箱拆箱的来源。

  充分理解装箱拆箱的原理,有助于我们程序员写出高效的代码。

  梳理下:前面DebugLZQ说到,所有值类型都继承自System.ValueType,而Sytem.ValueType继承自System.Object;所有值类型对象都分配在堆栈上,而所有引用类型,当然包括System.Object,对象都分配在堆上。那么,问题来了:既然System.Object 是所有值类型的基类,那么所有值类型必然可以隐式转换成System.Object(面向对象中的类型替换原则,基类能够替换子类),那么这个对象将被分配在哪里,堆上还是堆栈上?事实上,当这个转换发生时,CLR需要做额外的工作把堆栈上的值类型移动到堆上,这个操作就是被我们称作的“装箱”。

  装箱(box)的详细步骤:

  1. 在堆上分配一个内存空间,大小等于需要装箱的值类型对象的大小加上两个引用类型对象都拥有的成员:类型对象指针和同步块引用。
  2. 把堆栈上的值类型对象复制到堆上新分配的对象。
  3. 返回一个指向堆上新对象的引用,并且存储到堆栈上被装箱的那个值类型的对象里。

  这个步骤不需要程序员自己编写,在任何出现装箱的地方,编译器会自动加上执行以上功能的IL代码。

  所谓的拆箱就是装箱对应着的概念,但拆箱的过程和装箱并不是倒过来就是:

  拆箱(unbox.any)的详细步骤

  如果为待拆箱对象为null,抛出NullReferenceException异常。

  如果引用指向的不是一个期望对象的已装箱对象,抛出InvalidCastException异常。

  1. 获取已装箱对象中各个字段的地址,这个过程就是“拆箱”

  需要说明的是一般拆箱以后会伴随着对象的拷贝,但拷贝操作已经不是拆箱的范畴。

  装箱拆箱新能比较

  了解了装箱和拆箱的操作,我们可以清楚的明白:装箱操作会导致数据在堆和栈上进行拷贝,频繁的装箱操作会性能损失。而相比而言拆箱过程对性能损耗还是比较小的。

  3 小结

  装箱和拆箱意味着堆和堆栈空间的一系列操作,毫无疑问,这些操作的性能代价是很大的,尤其是对于堆上空间的操作,速度相对于堆栈慢得多,并且可能引发垃圾回收,这些都将大规模的影响系统系能。

  装箱和拆箱操作经常发生在以下连个场合:

  • 值类型的格式化输出
  • System.Object类型的容器

  第一种情况,类型的格式化输出往往伴随一次装箱操作,譬如:

using System;

namespace MaxValueTest
{
    /// <summary>
    /// DebugLZQ
    /// http://www.cnblogs.com/DebugLZQ
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            int i = Int32.MaxValue;
            Console.WriteLine("Int32的最大值是"+i);//引发了一次不必要的装箱操作
            Console.WriteLine("Int32的最大值是" + i.ToString());//ok

            Console.ReadKey();
        }
    }
}

  第二种情况更为常见一些,例如常用的容器ArrayList,就是一个典型的System.Object容器,任何值类型被放入到ArrayList的对象中,都会发生一次装箱操作,而对应的取出值类型对象会引发一次拆箱操作。

  在.NET 2.0以后,引入了“泛型”的概念后,这些问题得到了有效的解决。泛型允许定义针对某个特定类型(包括值类型)的容器,并且避免装箱和拆箱。

  关于泛型的机制和原理,请关注DebugLZQ后面的博文:《浅谈.NET中的泛型的机制和原理》,请期待~

时间: 2024-12-25 22:16:20

NET中的类型和装箱/拆箱原理的相关文章

6个重要的.NET概念:栈,堆,值类型,引用类型,装箱,拆箱

6个重要的.NET概念:栈,堆,值类型,引用类型,装箱,拆箱 引言 本篇文章主要介绍.NET中6个重要的概念:栈,堆,值类型,引用类型,装箱,拆箱.文章开始介绍当你声明一个变量时,编译器内部发生了什么,然后介绍两个重要的概念:栈和堆:最后介绍值类型和引用类型,并说明一些有关它们的重要原理. 最后通过一个简单的示例代码说明装箱拆箱带来的性能损耗. 声明变量的内部机制 在.NET程序中,当你声明一个变量,将在内存中分配一块内存.这块内存分为三部分:1,变量名:2,变量类型:3,变量值. 下图揭示了声

包装类型、装箱拆箱、基本类型速度比较

首先是包装类型 Long sum = Long.valueOf(0); long t1 = System.currentTimeMillis(); for (Long i = Long.valueOf(0); i < Integer.MAX_VALUE/2; i++) { sum += i; } t1 = System.currentTimeMillis() - t1; System.out.println("packaging took "+ t1 +" sum =

java中的包装类与装箱拆箱定义

JAVA 中int类型转String类型的通常方法,有三种:  1.String.valueOf(int i)  2.Integer.toString(int i)  3.i+"";     //i 为 int类型  这个称作包装类    Integer.valueOf("1").intValue();先把字符串1转换成int的包装类Integer后又通过.intValue()转换成值类    Integer.valueOf(1);这里是把int类型的1转换成int

[Java5新特性]自动装箱/拆箱

自动装箱/拆箱概述 Java中具有基本类型(int,double,float,long,boolean,char,byte,short)和基本类型包装类(Integer,Double,Float,Long,Boolean,Char,Byte,Short),我们实现基本类型与包装类之间的转换基本有两种方式: 一种为JDK5之前的方式,比如Integer i = Integer.valueof(5);(这里5为基本类型int,Integer包装类利用valueof()方法将其转换为Integer类型

读书笔记-C#中装箱拆箱性能

前言 最近在看王涛大神的<你必须知道的.NET(第二版)>一书,嗯,首先膜拜一下…. 在书中的第五章-品味类型中,对装箱与拆箱一节感触很深,概念本身相信每一个程序猿都不陌生,装箱是将值类型转换为引用类型 ,拆箱是将引用类型转换为值类型(ps:不小心又背了一下书),也知道装箱与拆箱过程中将带来性能上的问题,但是在实际的项目中往往会忽略这个问题,将可能带来极大的效率上的问题.问题有多大,反正我哭过. 简单对比测试 在工作之余写了个简单的测试例子,以HashTable.ArraryList.List

值类型&amp;引用类型,装箱&amp;拆箱

值类型:声明一个值类型变量,会在栈上分配一个空间,空间里存储的就是变量的值引用类型:声明一个引用类型变量,会在栈中分配一个空间,存储一个引用,这个引用指向了一个托管堆. 值类型:struct,枚举,数值类型,bool类型引用类型:数组,类,接口,委托(delegate),Object,string 可以看下下面的例子 public class Person { public string Name { get; set; } public int Age { get; set; } } publ

基本类型包装类、自动装箱拆箱

基本类型包装类 public class Demo03 { public static void main(String[] args) { //字符串转基本数据类型 String str="12"; int strint=Integer.parseInt(str); System.out.println(strint+1);  //13 String s2="2.3"; double dou=Double.parseDouble(s2); System.out.p

c#中装箱拆箱性能测试

c#中装箱拆箱性能测试 首先了解一下关于时间的换算: 1秒=1000毫秒: 1毫秒=1000微秒: 1微秒=1纳秒 而1毫秒=10000ticks:所以1ticks=100纳秒=0.1微秒 ticks这个属性值是指从0001年1月1日12:00:00开始到此时的以ticks为单位的时间,就是以ticks表示的时间的间隔数. 使用DateTime.Now.Ticks返回的是一个long型的数值. 然后上代码: using System; using System.Collections; usin

.Net中的装箱拆箱

说到装箱与拆箱,那就要简要的概括下什么是装箱拆箱, 装箱:其实就是把值类型转换为引用类型. 拆箱:其实就是把引用类型转换为值类型. 值类型:一般来说包括   int  char bool double datetime等等这些,值类型存放在栈里面 引用类型:引用类型包括  object  class  Interface Delegate  string arry  dynamic,引用类型是存放在堆里的,占用的内存地址一般是连续的. //装箱就是把值类型转换为引用类型 int i = 12;