装箱:

为了将一个值类型转换成一个引用类型,要使用一个名为装箱(boxing)的机制。下面总结了对值类型的一个实例进行装箱操作时在内部发生的事情。

1。在托管堆中分配好内存。分配的内存量是值类型的各个字段需要的内存量加上托管堆的所有对象都有的两个额外成员(类型对象指针和同步块索引)需要的内存量。

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

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

C#编译器会自动生成对一个值类型的实例进行装箱所需的IL代码,但你仍需理解内部发生的事情,否则很容易忽视代码长度问题和性能问题。

注意:
应该注意的是,FCL现在包含一组新的泛型集合类,它使非泛型的集合类成为“昨日黄花”。例如,应该使用System.Collection.Generic.List<T>类,而不要使用System.Collection.ArrayList类。泛型集合类在非泛型集合类的基础上进行了大量的增强。例如,API得到了简化和增强,集合类的性能也得到了显著提升。然而,最大的一个增强就是泛型集合类允许开发人员在操作值类型的集合时不需要对集合中的项进行装箱/拆箱处理。单单这一项设计,就是性能提升了不少。托管堆中需要创建的对象减少了,进而减少了应用程序需要执行的垃圾回收的次数。除此之外,开发人员还获得了编译时的类型安全性,源代码也因为强制类型转换的次数减少而变得更清晰。

拆箱:

假定需要使用以下代码获取ArrayList的第一个元素:

Point p=(Point)a[0];

现在是要获得ArrayList的元素0中的引用(或指针),并试图将其放到一个Point值类型的实例p中。为了做到这一点,包含在已装箱Point对象中的所有字段都必须复制到值类型变量p中,后者在线程栈上。CLR分两步完成这个复制操作。第一步是获取已装箱的Point对象中的各个Point字段的地址。这个过程称为拆箱(unboxing)。第二步是将这些字段包含的值从堆中复制到基于栈的值类型实例中。

拆箱不是直接将装箱过程倒过来。拆箱的代价比装箱低得多。拆箱其实就是获取一个指针的过程,改指针指向包含在一个对象中的原始值类型(数据字段)。事实上,指针指向的是已装箱实例中的未装箱部分。所以,和装箱不同,拆箱不要求在内存中复制任何字节。字段这个重要的区别之后,还应该知道的一个重点在于,往往会紧接着拆箱操作发生一次字段的复制操作

装箱、拆箱例子:

Point p;
p.x=p.y=;
Object o=p; //对p进行装箱;o引用已装箱的实例 //将Point的x字段变为2
p=(Point)o; //对o进行拆箱,并将字段从已装箱的实例复制到栈变量中
p.x=; //更改栈变量状态
o=p; //对p进行装箱;o引用新的已装箱实例

两句话三次装箱操作

Int32 v=;    //创建一个未装箱的值类型变量
Object o=v; //o引用一个已装箱的、包含值5的Int32
v=; //将未装箱的值改成123 Console.WriteLine(v + ", "+(Int32) o); //显示“123, 5”

应该指出的是,如果像下面这样写对WriteLine的调用,生成的IL代码将具有更高的执行效率:

Console.WriteLine(v +", "+o);  //显示“123, 5”

甚至可以这样调用WriteLine,进一步提升上述代码的性能:

Console.WriteLine(V.ToString()+", “+o); //显示“123, 5”

现在,会为未装箱的值类型实例v调用ToString方法,它返回一个String。String对象已经是引用类型,所以能直接传给Concat方法,不需要任何装箱操作。

 class Program
{
static void Main(string[] args)
{
#region Point
Point p1 = new Point(, );
Point p2 = new Point(, ); /*调用ToString()方法
* 在对ToString的调用中,p1不必装箱。从表面看,p1似乎必须装箱,因为ToString是从基类System.ValueType继承的一个虚方法。
* 通常,为了调用一个虚方法,CLR需要判断对象的类型,以定位类型的方法表。由于p1是一个未装箱的值类型,所以不存在“类型对象指针”。
* 然而,JIT编译器发现Point重写了ToString方法,所以会生成代码来直接(非虚)调用ToString方法,而不必进行任何装箱操作。
* 编译器知道这里不存在多态性的问题,因为Point是一个值类型,没有类型能从它派生,不可能存在该虚方法的其他实现。但是,
* 假如Point的ToString方法在内部调用base.ToString(),那么调用System.ValueType的ToString方法时,值类型的实例会被装箱。
*/
Console.WriteLine(p1.ToString());
/* 调用非虚方法GetType
* 调用非虚方法GetType时,p1必须进行装箱。Point的GetType方法是从System.Object继承的。
* 所以,为了调用GetType,CLR必须使用指向一个类型对象的指针,而这个指针只能通过对p1进行装箱来获得。
*/
Console.WriteLine(p1.GetType());
/* 调用CompareTo(第一次)
* 在CompareTo的第一次调用中,p1不必装箱,因为Point实现了CompareTo方法,编译器能直接调用它。注意,
* 我们像CompareTo传递了一个Point变量(p2),所以编译器会调用获取一个Point参数的CompareTo重载版本。
* 这意味着p2以传值方式传给CompareTo,无需装箱。
*/
Console.WriteLine(p1.CompareTo(p2));
/*转型为IComparable
* 将p1转型为接口类型的变量c时,p1必须装箱,因为接口被定义为引用类型。p1在装箱之后,指向已装箱对象的指针
* 会存储到变量c中。后面对GetType的调用证明c确实引用了堆上的一个已装箱的Point。
*/
IComparable c = p1;
Console.WriteLine(c.GetType());
/*调用CompareTo(第二次)
* 第二次调用CompareTo时,p1不需要装箱,因为Point实现了CompareTo方法,编译器能直接调用它。注意,此时向
* CompareTo传递的是一个IComparable类型的变量c,所以编译器会调用获取一个Object参数的CompareTo重载版本。
* 这意味着传递的实参必须是一个指针,它必须引用堆上的一个对象。幸好,c确实引用一个已装箱的Point,所以c中的内
* 存地址能直接传递给CompareTo,无需额外进行装箱。
*/
Console.WriteLine(p1.CompareTo(c));
/*调用CompareTo(第三次)
* 第三次调用CompareTo时,c引用的已经是堆上的的一个已装箱Point对象。由于c是IComparable接口类型,所以只能
* 调用接口的需要获取一个Object参数的CompareTo方法。这意味着传递的实参必须是引用了堆上的一个对象的指针。
* 所以,p2会被装箱,指向这个已装箱对象的指针将传给CompareTo。
*/
Console.WriteLine(c.CompareTo(p2));
/*转型为Point
* 将c转型为一个Point时,c引用的对上的对象会被拆箱,其字段会从堆复制到p2中。p2是栈上的一个Point类型的实例。
*/
p2 = (Point)c;
Console.WriteLine(p2.ToString());
#endregion
Console.ReadKey();
}
}
internal struct Point : IComparable
{
private int m_x,m_y;
//构造器负责初始化字段
public Point(Int32 x, Int32 y)
{
m_x = x;
m_y = y;
} //重写从System.ValueType继承的ToString方法。
public override String ToString()
{
return String.Format("( {0} , {1} )", m_x, m_y);
} public Int32 CompareTo(Point other)
{
return Math.Sign(Math.Sqrt(m_x * m_x + m_y * m_y) - Math.Sqrt(other.m_x * other.m_x + other.m_y * other.m_y));
} public Int32 CompareTo(Object o)
{
if(GetType()!=o.GetType())
{
throw new ArgumentException("o is not a Point");
}
return CompareTo((Point)o);
}
}

前面说过,未装箱值类型是比引用类型更”轻型“的类型。这要归结于以下两个原因。

  • 它们不在托管堆上分配。
  • 它们没有堆上的每个对象都有的额外成员,也就是一个”类型对象指针“和一个”同步块索引“。

由于未装箱的值类型没有同步块索引,所以不能使用System.Threading.Monitor类型的各种方法(或者使用C#的lock语句)让多个线程同步对这个实例的访问。

注:上述文字皆摘自CLR via C#

CLR via C# 中关于装箱拆箱的摘录的更多相关文章

  1. Java中的装箱拆箱

    一)  装箱与拆箱 Java中有概念是一切皆对象,因为所有的类都默认继承自Object.但是,对于数据类型是个例外,如short,int,long,float,double, byte,char,bo ...

  2. C#中的装箱拆箱

    在C#中,经常需要把值类型和引用类型相互转换. 首先明确两条法则: 1.引用类型总是被分配到“堆”上. 2.值类型总是分配到它声明的地方: a.作为引用类型的成员变量分配到“堆”上 b.作为方法的局部 ...

  3. QVariant实质 (类似 C#中的装箱拆箱)

    QVariant是一种可以存储不同类型的数据结构,在很多场合这是很有用得为了达到这种目的,可以想象,该对象应该存储对象的类型信息,数据信息以及其他辅助详细考虑用途,这种对象必须支持对不同对象的存储,对 ...

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

    谈到装箱拆箱,DebugLZQ相信给位园子里的博友一定可以娓娓道来,大概的意思就是值类型和引用类型的相互转换呗---值类型到引用类型叫装箱,反之则叫拆箱.这当然没有问题,可是你只知道这么多,那么Deb ...

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

    前言   最近在看王涛大神的<你必须知道的.NET(第二版)>一书,嗯,首先膜拜一下….     在书中的第五章-品味类型中,对装箱与拆箱一节感触很深,概念本身相信每一个程序猿都不陌生,装 ...

  6. WPF中多线程统计拆箱装箱和泛型的运行效率

    WPF中多线程统计拆箱装箱和泛型的执行效率.使用的知识点有泛型.多线程.托付.从样例中能够看到使用泛型的效率至少提升2倍 MainWindow.xaml <Window x:Class=&quo ...

  7. 如何理解Java中的自动拆箱和自动装箱?

    小伟刚毕业时面的第一家公司就被面试官给问住了... 如何理解Java中的自动拆箱和自动装箱? 自动拆箱?自动装箱?什么鬼,听都没听过啊,这...这..知识盲区... 回到家后小伟赶紧查资料,我透,这不 ...

  8. Java中的自动装箱拆箱

    Java中的自动装箱拆箱 一.自动装箱与自动拆箱 自动装箱就是将基本数据类型转换为包装类类型,自动拆箱就是将包装类类型转换为基本数据类型. 1 // 自动装箱 2 Integer total = 90 ...

  9. C#基础复习(2) 之 装箱拆箱

    参考资料 [1] @只增笑耳Jason的回答 https://www.zhihu.com/question/57208269 [2] <C# 捷径教程> 疑难解答 装箱和拆箱是什么? 何时 ...

随机推荐

  1. a标签与js的冲突

    如上图,需要做一个页面,点击左边的标题,右边就显示左边标题下的子标题的集合, html代码如下: <div id="newleft"> <ul> <l ...

  2. windows sqlplus客户端连接oralce数据库中文显示问题

    运行环境 服务器:centos6.8 服务器oracle版本:oracle 11g R2 64位,字符集是ZHS32utf8. 客户端:navicat 12x64  windows8.1x64 问题分 ...

  3. Golang入门教程(十四)结构体和类详解

    golang中并没有明确的面向对象的说法,实在要扯上的话,可以将struct比作其它语言中的class. 类声明 type Book struct { Title string Author stri ...

  4. awk指定[]为分隔符

    root@standby[16:38:35]$ root@standby[16:38:35]$ cat tmp.log qq [8.8.8.8] 6.6.6.6 [1502301436.016] &q ...

  5. Left join update和 ROW_NUMBER

    (1)Left join 更新update a set a.name=b.name from a left join b on a.id=b.stuid(2)ROW_NUMBERselect ROW_ ...

  6. HDB3 译码器

    一.HDB3译码方案一: 插入V/B的情况是有两种,两个或三个零两端同极性,也就是要把代码二进制表示的“+1 0 0 0 +1”或“-1 0 0 0 -1”变成“1 0 0 0”,把“+1 0 0 + ...

  7. 十四、文件和目录——文件时间和utime函数

    14.1 文件时间 存在于 stat 结构体中 14.2 文件时间函数 14.2.1 介绍 utime(修改文件的存取时间和更改时间) 相关函数 utimes,stat #include <sy ...

  8. Docker 添加环境系统文件配置

    在 docker 启动文件添加默认环境系统配置 " /etc/default/docker ": 添加  Environment File 配置: # vi /usr/lib/sy ...

  9. spring cloud心跳检测自我保护(EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.)

    EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER ...

  10. Python转义字符&字符串运算符

    Python转义字符 在需要在字符中使用特殊字符时,python用反斜杠(\)转义字符.如下表: 转义字符 描述 \(在行尾时) 续行符 \\ 反斜杠符号 \' 单引号 \" 双引号 \a ...