CLR via 笔记 5.3 值类型的装箱和拆箱
1.装箱
为了将一个值类型转换成一个引用类型,要使用一个名为装箱(Boxing)的机制。
1.在托管堆中分配好内存。分配的内存量是值类型的各个字段需要的内存量加上托管堆的所有对象都有的两个额外成员(类型对象指针和同步块索引)需要的内存量。
2.值类型的字段复制到新分配的堆内存。
3.返回对象的地址。现在,这个地址是对一个对象的引用,值类型现在是一个引用类型。
2.拆箱
包含在已装箱对象中的所有字段都必须复制到值类型变量中,后者在线程栈上。CLR分两步完成这个复制操作。
第一步是获取已装箱的对象中的各个字段的地址。这个过程称为拆箱(Unboxing)。
第二步是将这些字段包含的值从堆中复制到基于栈的值类型实例中。
拆箱不是直接将装箱过程倒过来。拆箱的代价比装箱低得多。拆箱其实就是获取一个指针的过程,该指针指向包含在一个对象中的原始值类型(数据字段)。事实上,指针指向的是已装箱实例中的未装箱部分。所以,和装箱不同,拆箱不要求在内存中复制任何字节。知道这个重要的区别之后,还应知道的一个重点在于,问问会紧接着拆箱操作发生一次字段的复制操作。
显然,装箱和拆箱/复制操作会对应用程序的速度和内存消耗产生不利影响,所以应该注意编译器在什么时候生产代码来自动这些操作,并尝试手动编写代码,尽量避免自动生成代码的情况。
疑问:
大家如果装Resharper,每当我们把一个值类型和一个字符串拼接的话,他都会提示我们,无需把值类型ToString,如下图:

Resharper推荐使用第一种方法,那到底会不会有问题呢?
举个例子:
public static void Main()
{
Int32 v = ; //创建一个未装箱的值类型变量
Object o = v; //o引用一个已装箱的、包含值5的Int32
v = ; //将未装箱的值修改成123 Console.WriteLine(v + ", " + (Int32) o); //显示"123, 5"
}
大家猜猜这段代码发生了多少次装箱操作?如果说是3次,会不会觉得意外?
让我们仔细分析一下代码,理解具体发生的事情。
第一次装箱操作是第四行,把未装箱值类型实例(v)复给引用类型。
第二次和第三次其实就是字符串的拼接,WriteLine要求获取一个String对象,所以就必须采取某种方式对这些数据项进行合并,以创建一个String。
为了创建一个String,C#编译器生成代码来调用String对象的静态方法Concat。该方法有几个重载的版本,所有版本执行的操作都是一样的,唯一区别是参数数量,此次编译器选择的是Concat方法的下面这个版本:
public static String Concat(Object arg0, Object arg1, Object arg2);
也就是说第二次装箱是将未装箱值类型v变成Object类型,第三次是o强转成Int32,拆箱后再次装箱,并将新的已装箱实例的内存地址传递给Concat的arg2参数。Concat方法调用指定的每个对象的ToString方法, 并连接每个对象的字符串表示。从Concat返回的String对象随即传给WriteLine方法,以显示最终的结果。
应该指出的是,如果想下面这样写对WriteLine的调用,生成的IL代码将具有更高的执行效率:
Console.WriteLine(v + ", " + o); //显示“123, 5”
这和前面的版本几乎完全一致,只是移除了变量o之前的(Int32)强制转型,避免了一次拆箱和一次装箱,减少了额外对象的产生和相应的垃圾回收,将具有更高的效率。
解答:
通过ILdasm工具,我们来看一下,到底发生了什么:

这下大家明白了吧,它在IL处理的时候,还是偷偷进行装箱,所以事实证明,这个ToString方法不能省!所以有些时候也不能完全按照第三方插件的提示。
推荐的方法:
Console.WriteLine(v.ToString() + ", " + o); //显示“123, 5”
现在会为未装箱的值类型实例v调用ToString方法,它返回一个String。String对象已经是引用类型,所以能直接传给Concat方法,不需要任何装箱操作。
PS:
当然Console.WriteLine方法默认有多个针对值类型的重载,目的是为了减少装箱的次数。

但是这些方法不可能为所有种情况都考虑到,就如自定义的值类型或者如上面的例子等。建议大家在自己写代码是手动进行值类型的装箱操作,显示指明类型,避免CLR生成低效率的IL代码。
CLR via 笔记 5.3 值类型的装箱和拆箱的更多相关文章
- [CLR via C#]5.3 值类型的装箱和拆箱
原文:[CLR via C#]5.3 值类型的装箱和拆箱 在CLR中为了将一个值类型转换成一个引用类型,要使用一个名为装箱的机制. 下面总结了对值类型的一个实例进行装箱操作时内部发生的事: 1)在托管 ...
- 【深入理解CLR】2:细谈值类型的装箱和拆箱
装箱 总所周知,值类型是比引用类型更“轻型”的一种类型,因为它们不作为对象在托管堆中分配,不会被垃圾回收,也不通过指针来引用.但在许多情况下,都需要获取对值类型的一个实例的引用.例如,假定要创建一个A ...
- 【.Net基础二】浅谈引用类型、值类型和装箱、拆箱
目前在看CLR via C#,把总结的记下来,索性就把他写成一个系列吧. 1.[.Net基础一] 类型.对象.线程栈.托管堆运行时的相互关系 2.[.Net基础二]浅谈引用类型.值类型和装箱.拆箱 引 ...
- 读经典——《CLR via C#》(Jeffrey Richter著) 笔记_值类型的装箱和拆箱(二)
[注意]:如果知道自己写的代码会造成编译器反复对一个值类型进行装箱,请改成用手动方式对值类型进行装箱. [好处]:代码会变得更小.更快. [例子]: using System; public seal ...
- [CLR via C#]值类型的装箱和拆箱
我们先来看一个示例代码: namespace ConsoleApplication1 { class Program { static void Main(string[] args) { Array ...
- [C#] 类型学习笔记一:CLR中的类型,装箱和拆箱
在学习.NET的时候,因为一些疑问,让我打算把.NET的类型篇做一个总结.总结以三篇博文的形式呈现. 这篇博文,作为三篇博文的第一篇,主要探讨了.NET Framework中的基本类型,以及这些类型一 ...
- 浅谈.NET中的类型和装箱、拆箱原理
谈到装箱拆箱,大概的意思就是值类型和引用类型的相互转换呗---值类型到引用类型叫装箱,反之则叫拆箱.这当然没有问题,可是你只知道这么多,那么建议你花点时间看看楼主这篇文章 1. .NET中的类型 为了 ...
- [ 转载 ]学习笔记-深入剖析Java中的装箱和拆箱
深入剖析Java中的装箱和拆箱 自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最基本的东西,再来看一下面试笔试中经常遇到的与装箱 ...
- .NET六大剑客:栈、堆、值类型、引用类型、装箱和拆箱
.NET六大剑客:栈.堆.值类型.引用类型.装箱和拆箱 一.“堆”,“栈”专区 这两个字我相信大家太熟悉了,甚至于米饭是什么?不知道...“堆”,“栈”是什么?哦,这个知道... 之前我也写过一篇堆栈 ...
随机推荐
- android.graphics(2) - Path, drawPath, moveTo, lineTo, addRect, addCircle, addOval, addArc, drawText, drawTextOnPath
一.创建路径 canvas中绘制路径利用: void drawPath (Path path, Paint paint) 1.直线路径 void moveTo (float x1, float y1) ...
- C# 改变无边框窗体尺寸大小的方法
; ; ; ; ; ; const int HTBOTTOMLEFT = 0x10; ; protected override void WndProc(ref Message m) { switch ...
- 632. Binary Tree Maximum Node【Naive】
Find the maximum node in a binary tree, return the node. Example Given a binary tree: 1 / \ -5 2 / \ ...
- python生成器,函数,数组
1.什么是生成器用一个比喻来形容,工厂中生产保龄球的流水线,机器每次只生产一个保龄球,下次继续生产下一个,直到停止(原料不足,停止供电等条件)为止.机器就是我们的生成器. 2.使用示例在python中 ...
- layui的点击table行选中复选框
$(document).on("click",".layui-table-body table.layui-table tbody tr",function() ...
- ios界面跳转
import Foundationimport UIKit class MyViewController: UIViewController{ // var window: UIWindow? ove ...
- 跟着百度学PHP[11]-PHP当中的异常处理
首先要说一下常见的三种错误: 1.语法错误 2.运行错误 3.逻辑错误 00x1 错误报告及错误级别 PHP的错误分为三个等级 1.注意(notice) 没有变量a 2.警告(warning) 没 ...
- lua工具库penlight--03字符串
字符串提取函数 这些方法也是从Python借鉴来的,但索引从1开始.stringx定义了一些函数如isalpha和isdigit, 用来判断字母和数字:startswith和endswith可以方便用 ...
- java web hello world(二)基于Servlet理解监听
java web最开始实现是通过Servlet实现,这里就来实现下,最原始的监听是如何实现的. 第一步,创建一个基本的web项目 ,参见(java web hello world(一)) 第二步,we ...
- shell 后台执行脚本
nohup command > myout.file 2>&1 &