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

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

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

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

效率更高的做法如下:

 SomeType st = obj as SomeType; 
 if (st != null ) 
 { 
 st.SomeTypeMethod(); 
}

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

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


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

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

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

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

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


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

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

 什么是值类型和引用类型

  • 什么是值类型:
    • 进一步研究文档,你会发现所有的结构都是抽象类型System.ValueType的直接派生类,而System.ValueType本身又是直接从System.Object派生的。根据定义所知,所有的值类型都必须从System.ValueType派生,所有的枚举都从System.Enum抽象类派生,而后者又从System.ValueType派生。  
    •  所有的值类型都是隐式密封的(sealed),目的是防止其他任何类型从值类型进行派生。       
  • 什么是引用类型:
    • 在c#中所有的类都是引用类型,包括接口。

区别和性能

  • 区别:
    • 值类型通常被人们称为轻量级的类型,因为在大多数情况下,值类型的的实例都分配在线程栈中,因此它不受垃圾回收的控制,缓解了托管堆中的压力,减少了应用程序的垃圾回收的次数,提高性能。
    • 所有的引用类型的实例都分配在托管堆上,c#中new操作符会返回一个内存地址指向当前的对象。所以当你在创建个一个引用类型实例的时候,你必须要考虑以下问题:
      • 内存是在托管堆上分配的
      • 在分配每一个对象时都会包含一些额外的成员(类型对象指针,同步块索引),这些成员必须初始化
      • 对象中的其他字节总是设为零
      • 在分配对象时,可能会进行一次垃圾回收操作(如果托管堆上的内存不够分配一次对象时)
  • 性能:
    • 在设计一个应用程序时,如果都是应用类型,那么应用程序的性能将显著下降,因为这会加大托管堆的压力,增加垃圾回收的次数。
    • 虽然值类型是一个轻量级的类型,但是如果大量的使用值类型的话,也会有损应用程序的性能(例如下面要讲的装箱和拆箱操作,传递实例较大的值类型,或者返回较大的值类型实例)。
    • 由于值类型实例的值是自己本身,而引用类型的实例的值是一个引用,所以如果将一个值类型的变量赋值给另一个值类型的变量,会执行一次逐字段的复制,将引用类型的变量赋值给另一个引用类型的变量时,只需要复制内存地址,所以在对大对象进行赋值时要避免使用值类型。
class SomRef

{

  public int x;

}

struct SomeVal {



public int x;



}



class Program {



static void ValueTypeDemo() {



SomRef r1 = new SomRef();//在堆上分配



SomeVal v1 = new SomeVal();//在栈上分配



r1.x = 5;//提领指针



v1.x = 5;//在栈上修改



SomRef r2 = r1;//只复制引用(指针)



SomeVal v2 = v1;//在栈上分配并复制成员



}



}

引用类型分配在托管堆上,值类型分配在线程栈上:其实这种说法的前半部分是对的,后半部分是错的。因为变量的值在它声明的位置存储的,所以假如某一个引用类型中有一个值类型的变量, 那么该变量的值总是和该引用类型的对象的其它数据在一起,也就是分配在堆上。(只有局部变量(方法内部声明的变量)和方法的参数在栈上)

对象在c#中默认的是用引用传递的:其实在调用方法的时候,参数值(对象的一个引用)是以传值得方式传递的,如果你想以引用方式传递的话,可以使用ref或者out关键字。

  • 使用非泛型集合时:比如ArrayList,因为这些集合需要的对象都是object,如果你将一个值类型的对象添加到集合中时会执行一次装箱操作,当你取值时会执行一次拆箱操作,所以在应用程序中应避免使用这种非泛型的集合。
  • 大家都知道System.Object是所有类型的基类,当你调用object类型的非虚方法时会进行装箱操作(例如GetType方法)。在调用object的虚方法时,如果你的值类型没有重写虚方法也要进行装箱操作,所以在定义自己的值类型时,应重写object内部的虚方法(例如ToString方式)

使用ArrayList时,每添加一个时间都会进行一次装箱操作,而使用List<DateTime>时就不会进行装箱操作,从而提高应用程序的性能。

 C#中常见的泛型集合:

Queue<T>;

Stack<T>;

List<T>;

Dictionary<Tkey,Tvalue>;

HashSet<T>;

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT技术猿猴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值