网页教学网
 当前位置: 网页教学网 >> 网络编程 >> ASP.NET编程技术 >> 阅读:《Effective C#》之减少装箱和拆箱
[ HTML ] [ FW ] [ DW ] [ FP ] [ JS ] [ XML ] [ CSS ] [ 图象 ] [ FLASH ] [ .NET ] [ ASP ] [ JSP ] [ PHP ] [ 数据 ] [ 系统 ] [ 安全 ] [ 素材 ] [ 建站 ] [ 主机 ] [ 入门 ] [ 技巧 ]

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

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

  例如,对于如下简单的装箱和拆箱操作语句。

  int i = 123;
  object obj = i;//Boxing
  if( obj is int )
  int j = (int) obj;//Unboxing

  为了,更好的诠释装箱和拆箱操作,我借用MSDN关于“Boxing”的解释图,具体如下。

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

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

  原因有两个,主要是关于效率:一个就是对于堆的操作效率比较低;另一个就是对于堆上分配的内存资源,需要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,属于引用类型,那么相当于临时变量就是原对象的引用,那么对于对于它的修改会直接修改到原对象,因此是可以的。可以说这里的不同本身在于产生临时对象的类型不同,从而造成本质的区别。

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

  对于装箱和拆箱操作来说,基本上就讲完了,只要记住频繁装箱和拆箱操作会降低程序效率,因此在编写的时候要尽量避免。
关键词:网络编程,c#
推荐给好友】【关闭】【收藏本文
最新五条评论
查看全部评论
评论总数 0
您的评论
用户名: 新注册) 密 码: 匿名:
·用户发表意见仅代表其个人意见,并且承担一切因发表内容引起的纠纷和责任
·本站管理人员有权在不通知用户的情况下删除不符合规定的评论信息或留做证据
·请客观的评价您所看到的资讯,提倡就事论事,杜绝漫骂和人身攻击等不文明行为