《Effective C#》之减少装箱和拆箱_天极网 http://dev.yesky.com/msdn/359/3486359.shtml

《Effective C#》之减少装箱和拆箱

2007-08-12 11:00  作者:愚翁
  为了便于文章的开展,首先介绍装箱(Boxing)和拆箱(Unboxing)这两个名词。.Net的类型分为两种,一种是值类型,另一种是引用类型。这两个类型的本质区别,值类型数据是分配在栈中,而引用类型数据分配在堆上。那么如果要把一个值类型数据放到堆上,就需要装箱操作;反之,把一个放在堆上的值类型数据取出来,则需要进行拆箱操作。

  例如,对于如下简单的装箱和拆箱操作语句。
       int i = 123;
  object obj = i;//Boxing
  if( obj is int )
  int j = (int) obj;//Unboxing
为了,更好的诠释装箱和拆箱操作,我借用MSDN关于“Boxing”的解释图,具体如下。

明白了这两名词的意思,现在说说为什么要减少装箱和拆箱操作。

  原因有两个,主要是关于效率:一个就是对于堆的操作效率比较低;另一个就是对于堆上分配的内存资源,需要GC来回收,从而降低程序效率。

考虑到这两点因素,那么需要在程序中减少装箱和拆箱操作。

  如何减少呢,涉及到这两个操作比较多的是,格式化输出操作,例如:String.Format,Console.WriteLine之类的语句。

  例如:

Console.WriteLine( "Number list:{0}, {1}, {2}",1,2,3 );

  
  对于“1,2,3”来说,相当于前面的“123”一样,需要经过装箱和拆箱两个操作。那么如何避免呢,其实只要向WriteLine传递引用类型数据即可,也就是按照如下的方式。

  Console.WriteLine( "Number list:{0}, {1}, {2}", 1.ToString(),2.ToString(),3.ToString() );

由于“1.ToString()”的结果是String类型,属于引用类型,因此不牵扯装箱和拆箱操作。

  其次,牵扯到装箱和拆箱操作比较多的就是在集合中,例如:ArrayList或者HashTable之类。

  把值类型数据放到集合中,可能会出现潜在错误。例如:

public struct Person
{
 private string _Name;
 public string Name
 {
  get{ return _Name; }
  set{ _Name = value; }
 }
 public Person( string PersonName )
 {
  _Name = PersonName;
 }

 public override string ToString()
 {
  return _Name;
 }
}

// Using the person in a collection

ArrayList arrPersons = new ArrayList();
Person p = new Person( "OldName" );
arrPersons.Add( p );

// Try to change the name

p = ( Person ) arrPersons[0] ;
p.Name = "NewName";
Debug.WriteLine( ( (Person ) arrPersons[0] ).Name );//It's "OldName"

  这个问题其实在前面的文章中已经讲过了。有人可能会说,是否可以按照如下的方式去修改呢。

( (Person ) arrPersons[0] ).Name = "NewName";//Can't be compiled

很不幸,如上操作不能通过编译。为什么呢,对于“( (Person ) arrPersons[0] )”来说,是系统用一个临时变量来接收拆箱后的值类型数据,那么由于值类型是分配在栈上,那么操作是对实体操作,可是系统不允许对一个临时值类型数据进行修改操作。

// Using the person in a collection

ArrayList arrPersons = new ArrayList();
Person p = new Person( "OldName" );
arrPersons.Add( p );

// Try to change the name

p = ( Person ) arrPersons[0] ;
p.Name = "NewName";
arrPersons.RemoveAt( 0 );//Remove old data first
arrPersons.Insert( 0, p );//Add new data
Debug.WriteLine( ( (Person ) arrPersons[0] ).Name );//It's "NewName"

  其实,这样操作会产生过多装箱和拆箱操作。那么更好的方法,可以通过接口来完成,从而减少装箱和拆箱操作。对于这个例子的接口实现应该如下。

public interface IPersonName
{
 string Name{ get;set;}
}

public struct Person:IPersonName
{
 private string _Name;
 public string Name
 {
  get{ return _Name; }
  set{ _Name = value; }
 }
 public Person( string PersonName )
 {
  _Name = PersonName;
 }

 public override string ToString()
 {
  return _Name;
 }
}

// Using the person in a collection

ArrayList arrPersons = new ArrayList();
Person p = new Person( "OldName" );

arrPersons.Add( p );
// Change the name

( (IPersonName)arrPersons[0] ).Name = "NewName";
Debug.WriteLine( ( (Person ) arrPersons[0] ).Name );//It's "NewName"

  很多人就问,为什么值类型不能修改,即

( (Person ) arrPersons[0] ).Name = "NewName";//Can't be compiled

  而如上的接口类型就能修改呢,即

( (IPersonName)arrPersons[0] ).Name = "NewName";

  这是由于产生的临时变量的类型不同,前者已经在前面进行说明了,后者由于产生的临时变量的类型为IPersonName,属于引用类型,那么相当于临时变量就是原对象的引用,那么对于对于它的修改会直接修改到原对象,因此是可以的。可以说这里的不同本身在于产生临时对象的类型不同,从而造成本质的区别。

  通过接口来改写,这样就减少了装箱和拆箱操作,同时也保证了修改的正确性。不过要注意的是,这里接口对于的是引用类型,如果接口访问的或者返回的是值类型,那么用接口虽说能实现了,但是对于装箱和拆箱操作来说,并没有减少。

  对于装箱和拆箱操作来说,基本上就讲完了,只要记住频繁装箱和拆箱操作会降低程序效率,因此在编写的时候要尽量避免。

https://tig.qpic.cn/doc/2018腾讯移动游戏技术评审标准与实践案例.pdf

Struct和Class的基本情况大致已经清楚,我们的真正目的是想知道如何架构我们的数据操作模块,才能充分地利用值类型和引用类型的,让效率最高(即装箱或拆线少,堆内存少)。

字符串拼接

数据结论

Struct在栈中不产生GC,class在堆中,会产生GC。对Struct的结点修改时,修改完以后记得重新赋值。因为Struct赋值是copy而不是引用,修改完以后,以前的不生效。

意义

堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些。结构表示轻量对象,并且结构的成本较低,适合处理大量短暂的对象。在表现抽象和多级别的对象层次时,类是最好的选择。大多数情况下该类型只是一些数据时,结构是最佳的选择。

C# | Boxing And Unboxing - GeeksforGeeks https://www.geeksforgeeks.org/c-sharp-boxing-unboxing/

// C# implementation to demonstrate
// the Boxing
using System;
class GFG {
  
    // Main Method
    static public void Main()
    {
  
        // assigned int value
        // 2020 to num
        int num = 2020;
  
        // boxing
        object obj = num;
  
        // value of num to be change
        num = 100;
  
        System.Console.WriteLine
        ("Value - type value of num is : {0}", num);
        System.Console.WriteLine
        ("Object - type value of obj is : {0}", obj);
    }

 

Value - type value of num is : 100
Object - type value of obj is : 2020

// C# implementation to demonstrate
// the Unboxing
using System;
class GFG {
  
    // Main Method
    static public void Main()
    {
  
        // assigned int value
        // 23 to num
        int num = 23;
  
        // boxing
        object obj = num;
  
        // unboxing
        int i = (int)obj;
  
        // Display result
        Console.WriteLine("Value of ob object is : " + obj);
        Console.WriteLine("Value of i is : " + i);
    }
}
  • Value of ob object is : 23
    Value of i is : 23
 
 
 


 
 
 
 
 
 
 
 

《Effective C#》之减少装箱和拆箱的更多相关文章

  1. C# 程序性能提升篇-1、装箱和拆箱,枚举的ToString浅析

    前景提要: 编写程序时,也许你不经意间,就不知不觉的使程序代码,发生了装箱和拆箱,从而降低了效率,不要说就发生那么一次两次,如果说是程序中发生了循环.网络程序(不断请求处理的)等这些时候,减少装箱和拆 ...

  2. Java暗箱操作之自动装箱与拆箱

    我以前在写Android项目的时候,估计写得最多最熟练的几句话就是: List<Integer> list = new ArrayList<Integer>(); list.a ...

  3. C#基础知识系列二(值类型和引用类型、可空类型、堆和栈、装箱和拆箱)

    前言 之前对几个没什么理解,只是简单的用过可空类型,也是知道怎么用,至于为什么,还真不太清楚,通过整理本文章学到了很多知识,也许对于以后的各种代码优化都有好处. 本文的重点就是:值类型直接存储其值,引 ...

  4. C# 知识回顾 - 装箱与拆箱

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

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

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

  6. 深入理解C#的装箱和拆箱(转)

    装箱和拆箱是值类型和引用类型之间相互转换是要执行的操作.  1. 装箱在值类型向引用类型转换时发生 2. 拆箱在引用类型向值类型转换时发生 光上述两句话不难理解,但是往深处了解,就需要一些篇幅来解释了 ...

  7. 转载:详解Java 自动装箱与拆箱的实现原理

    原文:http://www.jb51.net/article/111847.htm 什么是自动装箱和拆箱 自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对 ...

  8. CLR via 笔记 5.3 值类型的装箱和拆箱

    1.装箱 为了将一个值类型转换成一个引用类型,要使用一个名为装箱(Boxing)的机制. 1.在托管堆中分配好内存.分配的内存量是值类型的各个字段需要的内存量加上托管堆的所有对象都有的两个额外成员(类 ...

  9. [C#] 类型学习笔记一:CLR中的类型,装箱和拆箱

    在学习.NET的时候,因为一些疑问,让我打算把.NET的类型篇做一个总结.总结以三篇博文的形式呈现. 这篇博文,作为三篇博文的第一篇,主要探讨了.NET Framework中的基本类型,以及这些类型一 ...

随机推荐

  1. 解决Vue-router 报NavigationDuplicated的三种方法

    控制台会报[NavigationDuplicated {_name: "NavigationDuplicated", name: "NavigationDuplicate ...

  2. 关于Maven项目pom.xml文件不报错却有红叉的问题

    原因:spring-boot,升级到2.1.5版本,而maven-jar-plugin.version插件默认版本不兼容所以报错,但不影响运行 解决:在<properties></p ...

  3. jQuery EasyUI学习一

    1.   jQuery EasyUI介绍 1.  创建组件的方式和原理(掌握) 2.  组件三要素(掌握) 3.  Panel.LinkButton.上下文菜单;(掌握) 简介 2.1.  jQuer ...

  4. FAAS -- Serverless

    FAAS概念,无服务器运算,功能即服务,function-as-a-service 初创企业-大型企业.民间组织-政府机构 ===>>>> 上云 云计算第三代技术 -- Ser ...

  5. 数据库1 --- > 数据库概念、安装、卸载

    数据库概念 ​为什么学习数据库?1.web中的数据量非常大:2. 数据不方便存储和管理 ​什么是数据库: 用于存储和管理数据的仓库 数据库的特点: 数据可以实现持久化存储,其实数据库就是一个文件系统. ...

  6. mysql使用全文索引实现大字段的模糊查询

    0.场景说明 centos7 mysql5.7 InnoDB引擎 0.1创建表 DROP TABLE IF EXISTS tbl_article_content; CREATE TABLE tbl_a ...

  7. 【剑指 Offer】12.矩阵中的路径

    题目描述 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径.路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左.右.上.下移动一格. 如果一条路径经过了矩阵的某一格,那么 ...

  8. Flutter 布局类组件:线性布局(Row和Column)

    前言 所谓线性布局,即指沿水平或垂直方向排布子组件.Flutter中通过Row和Column来实现线性布局,并且它们都继承自弹性布局(Flex). 接口描述 Row({ Key key, // 表示子 ...

  9. /var/lib/zabbix/percona/scripts/get_mysql_stats_wrapper.sh: line 19: mysql: command not found

    [root@test ~]# tail -f /tmp/zabbix_agentd.log /var/lib/zabbix/percona/scripts/get_mysql_stats_wrappe ...

  10. 【Linux】history用法

    通过history命令可以查看我们在系统中输入过的命令 history命令的一些常用参数 -c  清空内存中命令历史 -d #  删除指定的历史命令,比如 history -d 100 ,就是删除第1 ...