装箱,拆箱,枚举,Tostring()+num.Tostring()为什么不属于装箱?

一、装箱拆箱概念: 
  
  这里是官方定义:http://msdn.microsoft.com/zh-cn/library/yz2be5wk.aspx 
  
  装箱:值类型→引用类型 
  
  拆箱:引用类型→值类型 
  
二、为什么说装箱,拆箱消耗资源(内存、cpu)? 
  
  2.1 图说装箱、拆箱 
  
                          
  
    说明:装箱。值类型存放于内存栈上,引用类型存放于内存对上。如果将已定义好的值类型(栈上的数据)修改至引用类型(堆上), 
  
  2.2 图文说 装箱过程 
  
    值类型存储(没有堆什么事):                             引用类型存储(栈中存储的是,堆中对象的地址,堆中是实际对象) 
  
            
  
    这时如果,将值类型变成引用类型,存储的位置发生变化,发生了装箱,而且为了拆箱,现在引用类型的存储模式也不仅仅是以上引用类型的存储模型了,值类型的类型也会进行相应的存储,以方便在拆箱时候,转换成相应装箱时的类型。 
  
    这样可以看出,装箱,其实比你直接定义成一个引用类型,给家消耗了内存,以及增加了计算量(消耗了cpu)。这是原理级别的解释,跟深入的,我也不太清除。只能分析到CLR这一步。 
  
三、浅谈ToString() 
  
  估计大家都知道,C#所有的类型基类(父类)均是Object,而Object中,提供的能叫子类继承的方法就那么几个,virtual 的ToString就是其中之一,所以说,c#中所有的类型均有这个ToString方法。下面就浅谈一下ToString方法在装箱拆箱中的一二。 
  
  3.1 针对普通值类型 
  
    以Int32为例(Struct) 
  
      int a=123; 
      string b=a.ToString(); 
  
        请问这是发生装箱了吗? 
  
      答:值类型→引用类型,oh,装箱!! 
  
      解答:只单纯的看装箱定义,这确实符合装箱的定义。但是,别忘记了ToString是基类的虚方法,子类是否对其有重写。 
  
        int 的 户口祖籍 
  
        int(C#语言)→Int32(CLR,oh是个结构,struct) 
  
          →extends System.ValueType(查看IL代码,发现了)→extends System.Object(这是终极祖宗啊!这里有ToString啊) 
  
        这是Int32中对ToString方法的重写: 
  
            public override string ToString() { return Number.FormatInt32(this, null, NumberFormatInfo.CurrentInfo);} 
  
        接下来就是内部的实现了,我去,看不到了啊?怎么办? 
  
      对了编写代码,查看IL代码。 
  
       
  
      可以看出这里没有发生装箱啊!具体的深入内部实现可以借助反编译工具,查看,如ILSpy、reflecter、ILdasm等。 
  
   
  
  3.2枚举类型 
  
    那么所有的值类型是不是使用Tostring方法,均不涉及装箱操作呢?这个也不尽然,可是尝试一下枚举类型。 
  
    枚举类型,是一个值类型。 
  
    示例: 
  
      enum TestEnum { Test1, Test2   } 
  
      string test = TestEnum.Test1.ToString(); //这句话是否发生装箱操作 
  
   
  
    3.2.1 内部原理 
  
      首先查看枚举中的ToString方法,这里重写了ToString方法 
  
      public override string ToString() { return InternalFormat((RuntimeType) base.GetType(), this.GetValue());} 
  
      查看InternalFormat方法的实现 
  
        private static string InternalFormat(RuntimeType eT, object value) 
        { 
            if (eT.IsDefined(typeof(FlagsAttribute), false)) 
            { 
                return InternalFlagsFormat(eT, value); 
            } 
            string name = GetName(eT, value); 
            if (name == null) 
            { 
                return value.ToString(); 
            } 
            return name; 
        } 
  
   
  
       通过查看可知eT.IsDefined(typeof(FlagsAttribute), false)、GetName(),这里使用了反射,可能会有性能的损失,但是不会有装箱操作 
  
       但GetName(eT,value),中的value参数是InternalFormat中的参数,这里的参数是object类型, 
  
        而InternalFormat((RuntimeType) base.GetType(), this.GetValue())调用时,这里的使用了this.GetValue这个方法来传递这个object参数 
  
       接下来查看 GetValue方法的实现啦 
  
         
  
      可以看出关于这个GetValue方法中发生了,装箱操作,return  (bool) *(((sbyte*) ptrRef)); 这个一个值类型,而GetValue需要的返回值是:Object类型 
  
    结论,枚举中重写的ToString方法不仅使用到了装箱操作,而且还是用到了大量的反射。 
  
    综上所述,使用枚举时,只是针对值类型操作,增加几个常量状态switch-case,以及不涉及取出枚举定义的值(ToString)则是非常方便的,快速的。 
  
      但是要是经常使用枚举的ToString取得枚举的定义值,则不建议使用。这里是非常不合时宜的。可以直接使用静态类代替即可(使用空间换取时间) 
  
  3.3 分析网络大牛的技术博客 
  
    原本装箱、拆箱感觉写的差不多了,但是看到网上那么多大牛、那么写感觉有点不合适啊!(不要被他们所谓的比较性能吓到哦) 
  
    3.3.1 博客地址:http://www.cnblogs.com/XmNotes/archive/2010/09/18/1830355.html 
  
      这是第一个:性能相差7千倍的ToString方法  的博客 
  
      解说:看到标题,第一句想说的是,我靠!这么雷人啊。7千倍啊! 
  
        但是一看代码你就知道他在干嘛了 
  
          var day = DayOfWeek.Wednesday; //这可是枚举啊 
  
          for (int i = 0; i < 1000000; i++)    {        value = day.ToString();    } 
  
        百万级别的反射、装箱。你坑人呢吧,不说实际有没有这么百万级别的数量和这么频繁的操作,就说有你这么用的吗! 
  
          一种是你直接返回一个值类型的星期,最后表现层给你转换一下,即使这里装箱、拆箱也就是这么一次两个,还能百万级别的刷啊! 
  
          还有就是类似你的第二种,直接就是操作引用类型的,如果像你这样百万级别的在转换一下,弄成静态常量。  
  
       结论是:举例要以事实做依据,不要做不符合实际的事情。不同的方法、类库用于适合的场景。这里不仅仅反射会耗时,装箱操作也会造成资源的消耗 
  
    3.3.2 博客地址:http://www.cnblogs.com/yjmyzz/archive/2010/09/19/1830766.html 
  
      这是第二个:也谈枚举ToString()性能的改进 的博客 
  
      解说:我不理解楼主在干吗,你定义的静态类,在第一次使用的时候,就已经将枚举装到静态变量dictionary中了,常驻内存了,直到程序结束才推出。 
  
          类似于你没事循环读取百万级别的一个静态变量啊! 
  
         而使用枚举的ToString方法,是你在百万级别的反射、装箱数据啊! 
  
        我晕啊!枚举是这样用,这样理解的吗? 
  
        如果这几个定义你常用、百万级别读取的话,你能不能稍微浪费点内存啊!直接这样用啊(空间换取时间) 
  
          public static class EnumLoginError 
          { 
              public static string 用户名不存在 { get{return "用户名不存在";}} 
              public static string 密码错误 { get{return "密码错误";}} 
              public static string 用户被锁定 { get{return "用户被锁定";}} 
              public static string 未知错误 { get{return "未知错误";}} 
          } 
      结论是:不要做画蛇添足的事情,对待事物要有怀疑精神。还是合适的工具做合适的活,合适的人做合适事情。 
  
四、能够减少拆箱装箱,常用的替代类库 
  4.1 推荐使用泛型集合 
  
    命名空间System.Collections.Generic 
    List<T>类似于ArrayList,ArrayList的升级版。 
      各种方法:Sort()、Max()、Min()、Sum()… 
    Dictionary<K,V>类似于Hashtable,Hashtable的升级版。 
    T,K,V就像一把锁,锁住集合只能存某种特定的类型,这里的T,K,V也可以是其它字母 

1 Struct A : ICloneable 
 2   { 
 3       public Int32 x; 
 4       public override String ToString() { 
 5         return String.Format("{0}",x); 
 6       } 
 7       public object Clone()  { 
 8         return MemberwiseClone(); 
 9       } 
10   } 
11   static void main() 
12   { 
13     A a; 
14     a.x = 100; 
15     Console.WriteLine(a.ToString()); 
16     Console.WriteLine(a.GetType()); 
17     A a2 = (A)a.Clone(); 
18     ICloneable c = a2; 
19     Ojbect o = c.Clone(); 
20   } 
21   5.0:a.ToString()。编译器发现A重写了ToString方法,会直接调用ToString的指令。因为A是值类型,编译器不会出现多态行为。
因此,直接调用,不装箱。(注:ToString是A的基类System.ValueType的方法)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值