减少装箱(Boxing)和拆箱(Unboxing)操作
为了便于⽂章的开展,⾸先介绍装箱(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之类的语句。
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,属于引⽤类型,那么相当于临时变量就是原对象的引⽤,那么对于对于它的修改会直接修改到原对象,因此是可以的。可以说这⾥的不同本⾝在于产⽣临时对象的类型不同,从⽽造成本质的区别。
通过接⼝来改写,这样就减少了装箱和拆箱操作,同时也保证了修改的正确性。不过要注意的是,这⾥接⼝对于的是引⽤类型,如果接⼝访问的或者返回的是值类型,那么⽤接⼝虽说能实现了,但是对于装箱和拆箱操作来说,并没有减少。
对于装箱和拆箱操作来说,基本上就讲完了,只要记住频繁装箱和拆箱操作会降低程序效率,因此在编写的时候要尽量避免。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。