c 装箱和拆箱,拆箱操作发生在什么时候

> 问题详情
装箱、拆箱操作发生在什么时候?
悬赏:0&答案豆
提问人:匿名网友
发布时间:
装箱、拆箱操作发生在什么时候?请帮忙给出正确答案和分析,谢谢!
论文写作技巧
网友回答(共2条)
匿名网友&&&&lv1&&&&提问收益:0.00&答案豆
对象与对象之间
匿名网友&&&&lv1&&&&提问收益:0.00&答案豆
引用类与值类之间
您可能感兴趣的试题
12.&什么时候可以用foreach语句代替while循环?23.&.net framwork通过什么与非托览代码进行交互操作
我有更好的答案
相关考试课程
请先输入下方的验证码查看最佳答案
图形验证:
验证码提交中……酷勤网 C 程序员的那点事!
当前位置: >
浏览次数:次
上一节我们讨论的是值类型和引用类型, 我们知道值类型是一种轻量级的数据结构, 对于一些简单的类型定义为值类型会减少定义成引用类型造成的系统开销以及GC的压力。但是值类型有一个缺点,就是缺少对象指针,我们不能用一个新的变量来引用栈上的值类型(Note:即未装箱的值类型)。也就是说很多引用类型为参数的方法不能传入值类型。为了解决这个问题,CLR提供了装箱和拆箱的机制。
一、装箱和拆箱的概念和原理
在面试中, 面试官提到装箱和拆箱的问题时,可能很多人想到的第一句话是&装箱是将值类型转化为引用类型的过程;拆箱是将引用类型转化为值类型的过程&。这句话没有问题,但是仅仅只说出这句话而没有下文的话那就不是一个中级.Net程序员的水平。
实际上装箱和拆箱这个名字就很形象,&箱&指的就是托管堆,装箱即指在托管堆中将在栈上的值类型对象封装,生成一份该值类型对象的副本,并返回该副本的地址。而拆箱即是指返回已装箱值类型在托管堆中的地址(注意:严格意义来说拆箱是不包括值类型字段的拷贝的)。
如果上面一段你仍然看的不是很明白的话,那么我们来看看装箱和拆箱过程中内部发生的事情。
#region 装箱和拆箱
int i = 10;
object o =
int j = (int)o;
#endregion
上面这段代码有一次拆箱和一次装箱。
装箱的过程为:
1. 分配内存: 在托管堆中分配好内存,内存的大小是值类型的各个字段需要的内存量加上托管堆的所有对象都有的两个额外成员&类型对象指针和同步块索引&所需要的内存量之和。
2. 复制对象: 将值类型的字段复制到新分配的内存中。
3. 返回地址: 将已装箱的值类型对象的地址返回给引用类型的变量。
我们来看看装箱的IL代码:
装箱操作有一个非常明显的标志,就是&box&,它的执行就是完成了我们刚才所说的三步。
拆箱的过程为:
1. 检查实例:首先检查变量的值是否为null,如果是则抛出NullReferenceException异常;再检查变量的引用指向的对象是不是给定值类型的已装箱对象,如果不是,则抛出InvalidCastException异常。
2. 返回地址:返回已装箱实例中属于原值类型字段的地址,而两个额外成员(类型对象指针和同步块索引)则不会返回。
到此,拆箱过程已经结束,但是伴随着拆箱,&往往&(《CLR via C#》中的描述,用的是&往往&,而并没有说一定,但是我带目前为止也不知道有没有一种拆箱没有伴随字段复制)会紧接着发生一次字段的复制操作。实际上就是讲已装箱对象中的实例字段拷贝到内存栈上。
下图为拆箱的IL代码:
同样,拆箱操作也有一个明显的标志:unbox。
1. 装箱和拆箱都是针对值类型而言的,而引用类型一致都是在托管堆中的,即总是以&装箱&的形式存在。
2. 装箱和拆箱并不是互逆的过程,实际上装箱的性能开销远比拆箱的性能开销大,并且伴随着拆箱的字段复制步骤实际上不属于拆箱。
3. 只有是值类型装箱之后的引用类型才能被拆箱,而并不是所有的引用类型都能被拆箱,将非装箱实例强制转化为值类型或者转化为非原装箱的值类型,会抛出InvalidCastException异常。
4. 拆箱的IL代码中有unbox和unbox.any两条指令,他们的区别是unbox指令不包含伴随着拆箱的字段复制操作,但是unbox.any则包含伴随着拆箱的字段复制操作。我到目前为止没有发现C#中有没有字段复制操作的拆箱,所以有时候也把这部操作放在拆箱的步骤里。
5. 在我们拆箱前怎么知道这个引用类型是否是期望的那个值类型的装箱形式呢。我们有两种方法,一种是用is/as操作符来判断(详情请移步:);还有一种方法是object类的GetType方法。
二、常见的拆箱和装箱场合
先看看下面这段代码,看看其中出现了多少次装箱:
&static void Main(string[] args) {
#region 装箱场合
int i = 2;
i.GetType();
object o =
ArrayList al = new ArrayList();
al.Add(i);
Hashtable ht = new Hashtable();
ht.Add(3, i);
Console.WriteLine(i + &, & + (int)o);
Console.ReadKey();
#endregion }
如果我说上面这段代码装箱了7次,你会不会觉得很意外?让我们来具体的分析下这段代码。
第一行就是一个简单的赋值语句,没有问题。
第二行是调用GetType()方法,我们想到GetType方法是在object类型中的非虚方法,子类中不可重写。所以调用时一定是调用的Object类型的GetType方法,所以这里发生了一次装箱。
第三行将值类型变量i赋值给引用类型Object的变量o,也发生了一次装箱。这个较明显,基本都可以看出来。
第四行实例化了一个ArrayList的对象,第五行将变量 i 添加到ArrayList中。我们首先查看下ArrayList.Add方法需要传入的参数类型:
public virtual int Add(object value);
他需要接收的是Object类型,所以这里也需要对变量 i 进行装箱。
同样的,第六行和第七行也是实例化一个Hashtable对象,并且将 i 添加进去。Hashtable.Add方法同样需要两个Object类型的参数,所以第七行会将3 和 i 分别装箱。
&public virtual void Add(object key, object value);
&至此,以上代码已经装箱了5次。
第八行中调用了Console.WriteLine方法,他接收的是一个string值,但是在累加中遇到int类型,会隐式转换为string类型。该方法参数的第一部分 i 需要装箱,而第三部分中是先将object类型强制转化为int类型,再将int型装箱为string类型。所以这一步经过了两次装箱。
综上,我们看到了简简单单的这几行代码进行了多达8次的装箱,如果有兴趣,可以自己写写然后看IL代码数&box&的数目。
在我们的日常工作中,常见的隐形装箱主要集中在方法需要
1. 传入的是引用类型,但是我们传的值是值类型,这就会造成装箱。比较典型的例子就是ArrayList和Hashtable。还有另外两个特殊的方法就是Console.WriteLine方法和String.Format方法。
2. 值类型调用父类的方法。若调用的是基类的非虚方法,无论如何都会装箱;若调用的是虚方法,如果在值类型中重写了,那么就不会装箱,若没有重写,调用的仍然是基类的方法,那么这个值类型仍然会长相。类似于上例中的GetType方法。
三、如何避免装箱
我们之所以研究装箱和拆箱,是因为装箱和拆箱会造成相当大的性能损耗(相比之下,装箱要比拆箱性能损耗大),性能问题主要体现在执行速度和字段复制上。因此我们在编写代码时要尽量避免装箱和拆箱,常用的手段为:
1. 使用重载方法。为了避免装箱,很多FCL中的方法都提供了很多重载的方法。比如我们之前讨论过的Console.WriteLine方法,提供了多达19个重载方法,目的就是为了减少值类型装箱的次数。比如看下面的这段代码:
Console.WriteLine(3);
刚开始你可能绝的3会装箱为string类型,但是实际上这条语句不会进行装箱操作,是因为Console.WriteLine方法有一个重载的方法,参数就是一个int的值。
public static void WriteLine(int value);
&类似Console.WriteLine方法,还有System.IO.BinaryWriter的Write 方法,System.IO.TextWriter 的Write和WriteLine方法,System.Text.StringBuilder的Append和Insert方法等都提供了大量的重载方法,以减少装箱次数。
所以我们在实际的项目中,应该时刻注意装箱的情况,并且选用合适的重载方法避免装箱。
2. 使用泛型。因为装箱和拆箱的性能问题,所以在.NET 2.0中引用了泛型,他的主要目的就是避免值类型和引用类型之间的装箱和拆箱。我们常用的集合类都有泛型的版本,比如ArrayList对应着泛型的List&T&,Hashtable对应着Dictionary&TKey, Tvalue&。
关于泛型的知识不是本篇文章的重点,以后有机会再专门总结整理。
3. 如果在项目中一个值类型变量需要多次拆装箱,那么可以将这个变量提出来在前面显式装箱。比如下面这段代码:
int j = 3; ArrayList a = new ArrayList(); for (int i = 0; i & 100; i++) {
a.Add(j); }
可以修改为:
&int j = 3; object ob = ArrayList a = new ArrayList(); for (int i = 0; i & 100; i++) {
a.Add(ob); }
4. ToString。这点单独列出来是因为虽然小,但是很实用。虽然表面上看值类型调用ToString方法是要进行装箱的,因为ToString是从基类继承的方法。但是ToString方法是一个虚方法,值类型一般都重写了这个方法,所以调用ToString方法不会装箱。
之前说过String.Format方法容易造成装箱,避免的最佳方法就是在调用这个方法前将所有的值类型参数都调用一次ToString方法。
上一篇的值类型与引用类型(原文地址:),这一篇的装箱与拆箱是类型的两个重点,在切实的理解了这些概念之后,会对你以后的工作有非常大的帮助。
当然类型中还有一些其他的需要说明的内容,包括一些基础知识(参见),以及基元类型、string类型、对象的相等性、dynamic类型等内容不准备放在这个面试的系列中,有时间会另外写出来。
下一篇准备讲解下接口与类的联系和区别,对于面试中常考的抽象类和接口的区别也有所涉及。希望本系列的文章对大家有所帮助,有不对的地方或者需要和我讨论的可以直接在下面给我留言,我基本每天都会上博客园看看的。
& 相关主题:装箱和拆箱
boxing and unboxing
更高的性能:类型转换要求装箱和拆箱(boxing and unboxing),这个过程占用了进程的时间和降低了性能。使用泛型不要求类型转换和装箱(boxing and unboxing),这样做提升了运行时的性能。
基于11个网页-
值类型和引用类型转化的时候会存在装箱和拆箱。
Boxing and unboxing convert between value and reference types.
对于大部分情况来说,Pyrex 不需要不断对简单数据类型变量进行装箱(box) 和 拆箱(unbox) 操作,因此速度比 Python 更快。
For the most part, Pyrex runs faster than Python by avoiding the need to continuously box and unbox variables of simple data types.
泛型相当有用,因为他们提供更强的编译时类型检查,要求更少的数据类型之间的显式转换,并减少对于装箱拆箱的操作和运行时类型检查的需要。
Generics are useful because they provide stronger compile-time type checking, require fewer explicit conversions between data types, and reduce the need for boxing operations and run-time type checks.
$firstVoiceSent
- 来自原声例句
请问您想要如何调整此模块?
感谢您的反馈,我们会尽快进行适当修改!
请问您想要如何调整此模块?
感谢您的反馈,我们会尽快进行适当修改!C#中的“装箱”与“拆箱”-asp.net-电脑编程网C#中的“装箱”与“拆箱”作者:佚名 和相关&&
&& 装箱和拆箱:任何值类型、引用类型可以和object(对象)类型之间进行转换。装箱转换是指将一个值类型隐式或显式地转换成一个object类型,或者把这个值类型转换成一个被该值类型应用的接口类型(interface-type)。把一个值类型的值装箱,就是创建一个object实例并将这个值复制给这个object,装箱后的object对象中的数据位于堆中,堆中的地址在栈中。被装箱的类型的值是作为一个拷贝赋给对象的。如:
int i = 10;
object obj = //隐式装箱
object obj = object(i); //显式装箱
if(obj is int) //int
&&&&&& Console.WriteLine(“OK”);
Console.WriteLine(obj.GetType()); //System.Int32&&& 有两种方式来查看包装以后的引用对象中包装的原始数据的类型。要判断原始类型是否是某个给定的原子类型,用is;如果要返回一个字符串,可以用object类的GetType方法。
&&& 拆箱转换是指将一个对象类型显式地转换成一个值类型,或是将一个接口类型显式地转换成一个执行该接口地值类型。注意装箱操作可以隐式进行但拆箱操作必须是显式的。拆箱过程分成两步:首先,检查这个对象实例,看它是否为给定的值类型的装箱值。然后,把这个实例的值拷贝给值类型的变量。比如:
int i = 10;
object obj =
int j = (int)
有两种方式来查看包装以后的引用对象中包装的原始数据的类型。要判断原始类型是否是某个给定的原子类型,用is;如果要返回一个字符串,可以用object类的GetType方法。
&&& 拆箱转换是指将一个对象类型显式地转换成一个值类型,或是将一个接口类型显式地转换成一个执行该接口地值类型。注意装箱操作可以隐式进行但拆箱操作必须是显式的。拆箱过程分成两步:首先,检查这个对象实例,看它是否为给定的值类型的装箱值。然后,把这个实例的值拷贝给值类型的变量。比如:
int i = 10;
object obj =
int j = (int)
相关资料:|||||||C#中的“装箱”与“拆箱”来源网络,如有侵权请告知,即处理!编程Tags:                &                    Java中的自动装箱与拆箱 - ImportNew
自动装箱和拆箱从Java 1.5开始引入,目的是将原始类型值转自动地转换成对应的对象。自动装箱与拆箱的机制可以让我们在Java的变量赋值或者是方法调用等情况下使用原始类型或者对象类型更加简单直接。
如果你在Java1.5下进行过编程的话,你一定不会陌生这一点,你不能直接地向集合(Collections)中放入原始类型值,因为集合只接收对象。通常这种情况下你的做法是,将这些原始类型的值转换成对象,然后将这些转换的对象放入集合中。使用Integer,Double,Boolean等这些类我们可以将原始类型值转换成对应的对象,但是从某些程度可能使得代码不是那么简洁精炼。为了让代码简练,Java 1.5引入了具有在原始类型和对象类型自动转换的装箱和拆箱机制。但是自动装箱和拆箱并非完美,在使用时需要有一些注意事项,如果没有搞明白自动装箱和拆箱,可能会引起难以察觉的bug。
本文将介绍,什么是自动装箱和拆箱,自动装箱和拆箱发生在什么时候,以及要注意的事项。
什么是自动装箱和拆箱
自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。原始类型byte,short,char,int,long,float,double和boolean对应的封装类为Byte,Short,Character,Integer,Long,Float,Double,Boolean。
自动装箱拆箱要点
自动装箱时编译器调用valueOf将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值。
自动装箱是将boolean值转换成Boolean对象,byte值转换成Byte对象,char转换成Character对象,float值转换成Float对象,int转换成Integer,long转换成Long,short转换成Short,自动拆箱则是相反的操作。
何时发生自动装箱和拆箱
自动装箱和拆箱在Java中很常见,比如我们有一个方法,接受一个对象类型的参数,如果我们传递一个原始类型值,那么Java会自动讲这个原始类型值转换成与之对应的对象。最经典的一个场景就是当我们向ArrayList这样的容器中增加原始类型数据时或者是创建一个参数化的类,比如下面的ThreadLocal。
ArrayList&Integer& intList = new ArrayList&Integer&();
intList.add(1); //autoboxing - primitive to object
intList.add(2); //autoboxing
ThreadLocal&Integer& intLocal = new ThreadLocal&Integer&();
intLocal.set(4); //autoboxing
int number = intList.get(0); // unboxing
int local = intLocal.get(); // unboxing in Java
上面的部分我们介绍了自动装箱和拆箱以及它们何时发生,我们知道了自动装箱主要发生在两种情况,一种是赋值时,另一种是在方法调用的时候。为了更好地理解这两种情况,我们举例进行说明。
这是最常见的一种情况,在Java 1.5以前我们需要手动地进行转换才行,而现在所有的转换都是由编译器来完成。
//before autoboxing
Integer iObject = Integer.valueOf(3);
Int iPrimitive = iObject.intValue()
//after java5
Integer iObject = 3; //autobxing - primitive to wrapper conversion
int iPrimitive = iO //unboxing - object to primitive conversion
方法调用时
这是另一个常用的情况,当我们在方法调用时,我们可以传入原始数据值或者对象,同样编译器会帮我们进行转换。
public static Integer show(Integer iParam){
System.out.println(&autoboxing example - method invocation i: & + iParam);
//autoboxing and unboxing in method invocation
show(3); //autoboxing
int result = show(3); //unboxing because return type of method is Integer
show方法接受Integer对象作为参数,当调用show(3)时,会将int值转换成对应的Integer对象,这就是所谓的自动装箱,show方法返回Integer对象,而int result = show(3);中result为int类型,所以这时候发生自动拆箱操作,将show方法的返回的Integer对象转换成int值。
自动装箱的弊端
自动装箱有一个问题,那就是在一个循环中进行自动装箱操作的情况,如下面的例子就会创建多余的对象,影响程序的性能。
Integer sum = 0;
for(int i=1000; i&5000; i++){
上面的代码sum+=i可以看成sum = sum + i,但是+这个操作符不适用于Integer对象,首先sum进行自动拆箱操作,进行数值相加操作,最后发生自动装箱操作转换成Integer对象。其内部变化如下
sum = sum.intValue() +
Integer sum = new Integer(result);
由于我们这里声明的sum为Integer类型,在上面的循环中会创建将近4000个无用的Integer对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。因此在我们编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题。
重载与自动装箱
当重载遇上自动装箱时,情况会比较有些复杂,可能会让人产生有些困惑。在1.5之前,value(int)和value(Integer)是完全不相同的方法,开发者不会因为传入是int还是Integer调用哪个方法困惑,但是由于自动装箱和拆箱的引入,处理重载方法时稍微有点复杂。一个典型的例子就是ArrayList的remove方法,它有remove(index)和remove(Object)两种重载,我们可能会有一点小小的困惑,其实这种困惑是可以验证并解开的,通过下面的例子我们可以看到,当出现这种情况时,不会发生自动装箱操作。
public void test(int num){
System.out.println(&method with primitive argument&);
public void test(Integer num){
System.out.println(&method with wrapper argument&);
//calling overloaded method
AutoboxingTest autoTest = new AutoboxingTest();
int value = 3;
autoTest.test(value); //no autoboxing
Integer iValue =
autoTest.test(iValue); //no autoboxing
method with primitive argument
method with wrapper argument
要注意的事项
自动装箱和拆箱可以使代码变得简洁,但是其也存在一些问题和极端情况下的问题,以下几点需要我们加强注意。
对象相等比较
这是一个比较容易出错的地方,”==“可以用于原始值进行比较,也可以用于对象进行比较,当用于对象与对象之间比较时,比较的不是对象代表的值,而是检查两个对象是否是同一对象,这个比较过程中没有自动装箱发生。进行对象值比较不应该使用”==“,而应该使用对象对应的equals方法。看一个能说明问题的例子。
public class AutoboxingTest {
public static void main(String args[]) {
// Example 1: == comparison pure primitive – no autoboxing
int i1 = 1;
int i2 = 1;
System.out.println(&i1==i2 : & + (i1 == i2)); // true
// Example 2: equality operator mixing object and primitive
Integer num1 = 1; // autoboxing
int num2 = 1;
System.out.println(&num1 == num2 : & + (num1 == num2)); // true
// Example 3: special case - arises due to autoboxing in Java
Integer obj1 = 1; // autoboxing will call Integer.valueOf()
Integer obj2 = 1; // same call to Integer.valueOf() will return same
// cached Object
System.out.println(&obj1 == obj2 : & + (obj1 == obj2)); // true
// Example 4: equality operator - pure object comparison
Integer one = new Integer(1); // no autoboxing
Integer anotherOne = new Integer(1);
System.out.println(&one == anotherOne : & + (one == anotherOne)); // false
i1==i2 : true
num1 == num2 : true
obj1 == obj2 : true
one == anotherOne : false
值得注意的是第三个小例子,这是一种极端情况。obj1和obj2的初始化都发生了自动装箱操作。但是处于节省内存的考虑,JVM会缓存-128到127的Integer对象。因为obj1和obj2实际上是同一个对象。所以使用”==“比较返回true。
容易混乱的对象和原始数据值
另一个需要避免的问题就是混乱使用对象和原始数据值,一个具体的例子就是当我们在一个原始数据值与一个对象进行比较时,如果这个对象没有进行初始化或者为Null,在自动拆箱过程中obj.xxxValue,会抛出NullPointerException,如下面的代码
private static I
//NullPointerException on unboxing
if( count &= 0){
System.out.println(&Count is not started yet&);
缓存的对象
这个问题就是我们上面提到的极端情况,在Java中,会对-128到127的Integer对象进行缓存,当创建新的Integer对象时,如果符合这个这个范围,并且已有存在的相同值的对象,则返回这个对象,否则创建新的Integer对象。
在Java中另一个节省内存的例子就是,感兴趣的同学可以了解一下。
生成无用对象增加GC压力
因为自动装箱会隐式地创建对象,像前面提到的那样,如果在一个循环体中,会创建无用的中间对象,这样会增加GC压力,拉低程序的性能。所以在写循环时一定要注意代码,避免引入不必要的自动装箱操作。
如想了解垃圾回收和内存优化,可以查看本文
总的来说,自动装箱和拆箱着实为开发者带来了很大的方便,但是在使用时也是需要格外留意,避免引起出现文章提到的问题。
可能感兴趣的文章
不知道这个小log日志框架快在什么地方,为什么会快?
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:@
广告与商务合作QQ:
– 好的话题、有启发的回复、值得信赖的圈子
– 写了文章?看干货?去头条!
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 活跃 & 专业的翻译小组
– 国内外的精选博客文章
– UI,网页,交互和用户体验
– JavaScript, HTML5, CSS
– 专注Android技术分享
– 专注iOS技术分享
– 专注Java技术分享
– 专注Python技术分享
& 2017 ImportNew}

我要回帖

更多关于 c 装箱拆箱 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信