值类型



1、值类型是在栈中分配内存,在声明时初始化才能使用,不能为null



2、值类型超出作用范围系统自动释放内存



3、 主要由两类组成:结构,枚举(enum),结构分为以下几类:



  • 整型(Sbyte、Byte、Char、Short、Ushort、Int、Uint、Long、Ulong)
  • 浮点型(Float、Double)
  • decimal
  • bool
  • 用户定义的结构(struct)



引用类型



1、引用类型在堆中分配内存,初始化时默认为null



2、引用类型是通过垃圾回收机制进行回收



3、包括类、接口、委托、数组以及内置引用类型object与string



 



由于C#中所有的数据类型都是由基类System.Object继承而来的,所以值类型和引用类型的值可以通过显式(或隐式)操作相互转换,而这转换过程也就是装箱(boxing)和拆箱(unboxing)过程。



  1. 装箱 是值类型到 object 类型或到此值类型所实现的任何接口类型的隐式转换。对值类型装箱会在堆中分配一个对象实例,并将该值复制到新的对象中。 
  2. 拆箱(取消装箱)是从 object 类型到值类型或从接口类型到实现该接口的值类型的显式转换。取消装箱操作包括:
  1. 检查对象实例,确保它是给定值类型的一个装箱值。(拆箱后没有转成原类型,编译时不会出错,但运行会出错,所以一定要确保这一点。用GetType().ToString()判断时一定要使用类型全称,如:System.String 而不要用String。)
  2. 将该值从实例复制到值类型变量中。
  3. 3维装箱问题Python 三维箱柜装载概念_library

示例

首先写个简单的控制台程序:



// 
    Tutorial_boxing_unboxing.cs
 
   // 
    装箱与拆箱 
   
 
   using 
    System;

 
   class 
    App
{
     
   static 
     
   void 
    Main()
    {
         
   int 
    i  
   = 
     
   32 
   ;
         
   object 
    o  
   = 
    i;  
   // 
   隐式装箱 
   

         Console.WriteLine( 
   " 
   o = {0} 
   " 
   , o);

        Console.Read();
    }
}



其中object o = i这里我们进行了装箱操作,然后我们用MSIL 反汇编程序查看下生成的.exe程序的内部机理。



1  
   .method  
   private 
    hidebysig  
   static 
     
   void 
     Main() cil managed
 
    2  
   {
 
    3  
     .entrypoint
 
    4  
      
   // 
    代码大小       30 (0x1e) 
   
 
    5  
     .maxstack   
   2 
   
 
    6  
     .locals init ([ 
   0 
   ] int32 i,
 
    7  
              [ 
   1 
   ]  
   object 
    o)
 
    8  
     IL_0000:  nop
 
    9  
     IL_0001:  ldc.i4.s    
   32 
   
 
   10  
     IL_0003:  stloc. 
   0 
   
 
   11  
     IL_0004:  ldloc. 
   0 
   
 
   12  
     IL_0005:  box        [mscorlib]System.Int32
 
   13  
     IL_000a:  stloc. 
   1 
   
 
   14  
     IL_000b:  ldstr       
   " 
   o = {0} 
   " 
   
 
   15  
     IL_0010:  ldloc. 
   1 
   
 
   16  
     IL_0011:  call        
   void 
    [mscorlib]System.Console::WriteLine( 
   string 
   ,
 
   17  
                                                                    
   object 
   )
 
   18  
     IL_0016:  nop
 
   19  
     IL_0017:  call       int32 [mscorlib]System.Console::Read()
 
   20  
     IL_001c:  pop
 
   21  
     IL_001d:  ret
 
   22  
   }  
   // 
    end of method App::Main



其中第12行是我们的装箱操作。(关于IL中出现的操作符代表的操作请查阅MSDN Library中的.NET开发/.NET Framework SDK/类库参考/System.Reflection.Emit/OpCodes 类/OpCodes 字段)

然后我们取消装箱操作:



static 
     
   void 
    Main()
    {
         
   int 
    i  
   = 
     
   32 
   ;

        Console.WriteLine( 
   " 
   i = {0} 
   " 
   , i);

        Console.Read();
    }


再用MSIL工具查看生成的.exe,如下结果:


.method  
   private 
    hidebysig  
   static 
     
   void 
     Main() cil managed
{
  .entrypoint
   
   // 
    代码大小       28 (0x1c) 
   
 
     .maxstack   
   2 
   
  .locals init ([ 
   0 
   ] int32 i)
  IL_0000:  nop
  IL_0001:  ldc.i4.s    
   32 
   
  IL_0003:  stloc. 
   0 
   
  IL_0004:  ldstr       
   " 
   i = {0} 
   " 
   
  IL_0009:  ldloc. 
   0 
   
  IL_000a:  box        [mscorlib]System.Int32
  IL_000f:  call        
   void 
    [mscorlib]System.Console::WriteLine( 
   string 
   ,
                                                                 
   object 
   )
  IL_0014:  nop
  IL_0015:  call       int32 [mscorlib]System.Console::Read()
  IL_001a:  pop
  IL_001b:  ret
}  
   // 
    end of method App::Main



在IL_000a行,我们发现这里却也出现了一个box!不过这步是在call System.Console::WriteLine(string, object)时发生的。我们对比前面我们手动boxing的IL代码,发现在我们手动boxing后就没有这步box了。为什么呢?

当我们在调用一些方法的重载版本时,由于编译器找不到符合给定参数类型的重载方法,此时编译器便去寻找到的最接近的版本,然后使用找到的方法,而其参数却是我们传入的值类型的基类如System.Object或者其实现的接口类型,接着编译器为了求得与这个方法的原型一致,就必须对该值类型进行装箱操作(转换成引用类型)。

照这个说法当我们不手动boxing时,在调用了Console.WriteLine()方法输出一个Int32类型值时,系统就要自动进行 boxing。也就是说如果我们要对该输出操作作5000次的循环,系统就要做5000次的boxing。这样对性能便会有一定的影响,而且要使循环次数是100,000,000次呢,或者跟多!

此时我们便要想如何消除这不应该的性能损失!正如第一个程序是展示的,我们可以在需要的地方先进行boxing,这个原理很简单,我们可以联想到类似的做法:



// 
   当我们如下时: 
   
 
   for 
    ( 
   int 
    i  
   = 
     
   0 
   ; i  
   < 
    arr.Length; i 
   ++ 
   )
{
    
   // 
    

 
   }

 
   // 
   我们更因该这样: 
   
 
   int 
    L  
   = 
    arr.Length;
 
   for 
    ( 
   int 
    i  
   = 
     
   0 
   ; i  
   < 
    L; i 
   ++ 
   )
{
    
   // 
    

 
   }

这样,我们只要一次boxing,就可以避免让系统重复的做这个操作。

用途

像在调用Console.WriteLine()的过程中系统自动进行boxing一样,当我们在调用其它的一些方法的重载版本进行操所时,为了避免由于无谓的隐式装箱所造成的性能损失,在执行这些多类型重载方法之前,最好先对值进行装箱。一般是在处理大量数据需要对类型进行装箱操作。