装箱和拆箱存在的意义:值类型是数据的容器,它存储在堆栈上,不具备多态性,而.NET框架在整个对象层次的设计中,使用System.Object作为所有类型的基类,但是Obejct是引用类型,而作为值类型的基类System.ValueType,是从System.Object派生出来的,这就产生了矛盾,装箱和拆箱就是为了解决这两种类型之间的差异。

装箱会将一个值类型放入一个未具名类型(untyped)的引用对象中,从而允许该值类型应用于那些只能使用引用类型的场合。拆箱则会从前面的装箱对象中提取出一个值类型的副本。装箱和拆箱都是比较耗时的操作。

装箱操作会将值类型转换为一个引用类型,这个过程中会创建一个新的引用独享,然后将其分配到堆上,同时值类型的副本会被存储在该引用对象内部。当我们需要从装箱对象中获取任何信息时,会创建值类型的一个副本,然后将其返回。其中的关键是:当我们需要引用类型时,会创建一个新的引用类型的对象并将其放入到堆中;当我们需要访问已经装箱的对象信息时,就会创建对应值类型的一个副本,并将其返回。

装箱和拆箱最大的问题是它们会自动发生。当我们使用的是值类型,而期望的是引用类型,那么编译器就会自动产生装箱和拆箱语句。

我们来看下面的语句,居然也发生了装箱和拆箱操作。

Console.WriteLine("A few numbers:{0}, {1}, {2}",
25, 32, 50);

上述代码之所以发生了装箱,是因为WriteLine方法需要的参数类型是System.Object,而25是一个int类型,属于值类型,因此需要装箱,而在WriteLine方法内部实现时,需要调用方法参数的ToString()方法,为了调用装箱对象的方法,就会发生拆箱的操作。

为了避免装箱和拆箱,可以将上述代码进行如下修改。

Console.WriteLine("A few numbers:{0}, {1}, {2}",
25.ToString(), 32.ToString(), 50.ToString());

另外,由于装箱和拆箱都会产生新的实例,那么有时会产生一些诡异的bug,我们来查看下面的代码。

代码

1 public struct Person
2 {
3 private string _Name;
4
5 public string Name
6 {
7 get
8 {
9 return _Name;
10 }
11 set
12 {
13 _Name = value;
14 }
15 }
16
17 public override string ToString( )
18 {
19 Return _Name;
20 }
21 }
22
23  // Using the Person in a collection:
24 ArrayList attendees = new ArrayList( );
25 Person p = new Person( "Old Name" );
26 attendees.Add( p );
27
28 // Try to change the name:
29 // Would work if Person was a reference type.
30 Person p2 = (( Person )attendees[ 0 ] );
31 p2.Name = "New Name";
32
33 // Writes "Old Name":
34 Console.WriteLine(
35 attendees[ 0 ].ToString( ));

上述代码中,Person是一个值类型,在将其放入ArrayList时,会进行装箱操作,这时会有一次复制操作,当我们需要获得ArrayList内Person对象的信息时,需要一次拆箱,又会有一次复制操作,因此,当我们并没有对ArrayList内的对象进行修改,而是针对副本进行修改。

我们可以通过以下的方式来修改上述代码存在的问题。

代码

1 public interface IPersonName
2 {
3 string Name
4 {
5 get; set;
6 }
7 }
8
9 struct Person : IPersonName
10 {
11 private string _Name;
12
13 public string Name
14 {
15 get
16 {
17 return _Name;
18 }
19 set
20 {
21 _Name = value;
22 }
23 }
24
25 public override string ToString( )
26 {
27 return _Name;
28 }
29 }
30
31 // Using the Person in a collection:
32 ArrayList attendees = new ArrayList( );
33 Person p = new Person( "Old Name" );
34 attendees.Add( p ); // box
35
36 // Try to change the name:
37 // Use the interface, not the type.
38 // No Unbox needed
39 (( IPersonName )attendees[ 0 ] ).Name = "New Name";
40
41 // Writes "New Name":
42 Console.WriteLine(
43 attendees[ 0 ].ToString( )); // unbox
44
45

装箱后的引用类型实现了原来值类型对象上所有的接口,这意味着不会再发生复制,但是当我们调用IPersonName.Name属性时,它会将调用请求转发给“箱子”内部的值类型,在值类型上实现接口使我们可以访问”箱子“的内部,从而允许直接改变ArrayList中的信息。

总之,我们应该对任何将值类型转换为System.Object或者接口类型的构造保持密切的关注,例如将值类型放入集合中,在值类型上调用System.Object定义的方法等,这些操作都会将值类型转换为System.Object,只要有可能,我们都应该避免这种转换。

Effective C# Item17:尽量减少装箱和拆箱相关推荐

  1. 六个重要的.NET概念:栈、堆、值类型、引用类型、装箱和拆箱

    目录 介绍 当你声明一个变量时,里面有什么? 栈和堆 值类型和引用类型 那么哪些数据类型是引用类型,哪些是值类型? 装箱和拆箱 装箱和拆箱的性能含义 关于源代码 下载源代码 - 42.5 KB 介绍 ...

  2. C# 如何避免装箱和拆箱操作

    Net的类型分为两种,一种是值类型,另一种是引用类型.这两个类型的本质区别,值类型数据是分配在栈中,而引用类型数据分配在堆上.那么如果要把一个值类型数据放到堆上,就需要装箱操作;反之,把一个放在堆上的 ...

  3. C#中装箱和拆箱的作用 the effection of boxing and unboxing

    值类型和引用类型放在不同的地方,在互相转换的时候需要把数据搬过来搬过去,这就是装箱和拆箱了. 在于类型的转换 例如一个函数要处理传入的参数,但是这个参数可能是A.C.E这三种类型的类,那么一般做法就是 ...

  4. C# 装箱和拆箱[整理]

    1. 装箱和拆箱是一个抽象的概念 2. 装箱是将值类型转换为引用类型 :拆箱是将引用类型转换为值类型 利用装箱和拆箱功能,可通过允许值类型的任何值与Object 类型的值相互转换,将值类型与引用类型链 ...

  5. java可以用 lt =判断int吗_int 和 integer:装箱和拆箱的过程,会用到什么方法

    本文转载自[微信公众号:五角钱的程序员,ID:xianglin965],经微信公众号授权转载,如需转载与原文作者联系 原文标题<int 和 integer :装箱和拆箱的过程,会用到什么方法,你 ...

  6. [C#] C# 知识回顾 - 装箱与拆箱

    装箱与拆箱 目录 生活中的装箱与拆箱 C# 的装箱与拆箱 值类型和引用类型 装箱 拆箱 读者见解 生活中的装箱与拆箱    我们习惯了在网上购物,这次你想买本编程书 -- <C 语言从入门到放弃 ...

  7. Java自动装箱与拆箱

    欢迎支持笔者新作:<深入理解Kafka:核心设计与实践原理>和<RabbitMQ实战指南>,同时欢迎关注笔者的微信公众号:朱小厮的博客. 欢迎跳转到本文的原文链接:https: ...

  8. C#基础:理解装箱与拆箱

    .NET大牛之路 • 王亮@精致码农 • 2021.08.27 前面我们讲到 .NET 平台支持的两大数据类型:值类型和引用类型.值类型比引用类型更高效,因为它没有指针引用,不用分配在托管堆中,也不用 ...

  9. [019] C#基础:理解装箱与拆箱

    .NET大牛之路 • 王亮@精致码农 • 2021.08.27 前面我们讲到 .NET 平台支持的两大数据类型:值类型和引用类型.值类型比引用类型更高效,因为它没有指针引用,不用分配在托管堆中,也不用 ...

最新文章

  1. mybatis使用foreach实现sql的in查询
  2. c primer plus 第6版 中文版pdf_内功实力再精进 试驾上汽大通V80 PLUS城市版_搜狐汽车...
  3. rocketmq一个topic多个group_SpringBoot和RocketMQ的简单实例
  4. 网页html生成图片的常用方案
  5. Oracle安装过程中出现的错误指令
  6. 怎么打包python环境_python环境搭建和打包
  7. Java Web之过滤器的简单创建
  8. 苹果6怎样分屏_苹果凌晨更新,看片神器iPad一秒下线(文末有福利)
  9. CVPR、ECCV 2020 两大会议论文分类索引
  10. 让你的SpringBoot更有个性, 定制酷炫Banner
  11. 【zephyr】apds9660 接近(Proximity)传感器 驱动模型实现方式(一)
  12. 台式安装nas系统_从0开始使用“矿渣”低成本打造家庭NAS,黑群晖系统安装(中)...
  13. IDL是什么呢???
  14. 规律的生活,规律的学习
  15. Qt设计师如何添加QToolBar工具栏
  16. EXCEL VBA中变量的作用域 工程级变量使用注意小点
  17. mysql的force的作用_mysql中force Index等一些不是很常用的sql优化介绍
  18. 友盟分享 QQ分享后,不显示左下角的小图标
  19. Android实现收款成功金额的语音播报功能(Nice tone)
  20. ubunto下编译安装opencv方法

热门文章

  1. Exchange2010升级到2016——配置Exchange2016连接器、外部DNS设置
  2. Wince6.0应用开发:二、模拟器的使用
  3. 计算机设计类有哪些专业,2021新高考模式下报考,这4类专业有“潜规则”,考生报考需谨慎...
  4. 一台电脑两种jdk_同一个电脑安装两个jdk版本
  5. 水很深的深度学习(四)——卷积神经网络CNN
  6. 通俗易懂的monteCarlo积分方法(八)
  7. matlab结构体构建,matlab 怎么建立结构体数组?
  8. internal server error怎么解决_解决redis连接错误:MISCONF Redis is configured to save RDB
  9. AcWing 1750. 救生员(差分+暴力枚举)
  10. mac apache php.ini,Mac自带的Apache使用详解