“老生常谈”值类型与引用类型

  众所周知,.NET类型系统由 类、结构、枚举、接口 和 委托 组成。而根据内存分配的方式来区分,所有的类型又被分为 值类型 与 引用类型

 

    一说到值类型,大多数人都会自信地说,“值类型不就是 int,float,double...还有...额...还有啥来着?”。然后开始支支吾吾,似懂非懂,就像当初刚刚毕业的我面对面试官的提问,并且号称自己已有一年使用c#编程的经验(惭愧,惭愧)。

  值类型的确是包括了int,float...这些c#预定义的数值数据类型,它们也有共同的名称,叫做结构。结构和枚举都属于值类型,它们都隐式派生自System.ValueType。简而言之,System.ValueType的作用是确保所有派生类型(如任何结构)都分配在栈上而非垃圾回收堆上。创建和销毁分配在栈上的数据都很快,因为它的生命周期是由定义的作用域决定的。值类型就像是富土康在暑假期间招的学生临时工一样,直接从各个劳务中介那里一批批地拉进工厂,不需要像正式工那样复杂的入职手续,非常方便的就能上流水线操作,解决人力需求,别问我为什么知道这么多。

  有同学可能会问 int、float 怎么会是结构?easy,你把光标在vs中放在 int 上,你自然就会明白了。

   

 

   由于值类型基于值的语法,结构(也包括所有数值数据类型 int、float 等,以及任何枚举或自定义结构)的生命周期是可以预测的。当结构变量离开定义域的范围是,他就会立即从内存中移除:

复制代码
 1         //本地变量在方法返回时弹出栈 2         static void func()  3         {  4             //“int”其实是 System.Int32 结构 5             int i = 0;  6  7             //Point 是结构类型    8             Point p = new Point();  9 10         }//“i”和“p”在这里弹出栈11                     
复制代码

  

  引用类型则被创建在堆内存中,堆内存就像是一个混乱的监狱,这里的一切不再像栈那样井然有序,这时就必须要有一个管理者来维持秩序。当我们new一个类对象的时候,在堆内存中就会相应地开辟出一块空间来存放这个对象并返回该对象的引用(引用实际上可以用指向对象的指针来理解,有学习过c指针的同学会有同感),每次访问对象时,都是通过引用(指针)来找到相应的对象进行操作。刚创建的类对象好比就是被扔进监狱里的一个犯人,而每个犯人都有自己的牢房号,当有家属要来探访犯人时,狱警就会根据牢房号来找到对应的犯人,这里的“牢房号”指的就是对象的引用(指针)。

  ok,继续用监狱的犯人来打比方,A犯人的刑期已到,到了刑满释放的日子了(A对象的资源要被释放),那么监狱的管理者到时自然就会让狱警给A犯人登记出狱。然而这背后一切的秩序都是神秘的监狱管理者在管控着,堆内存中的神秘的管理者就是CLR(Common Language Runtime),它管理着托管堆中所有的对象资源,当对象的资源需要被释放时GC(Garbage Collection)就会回收对象的资源。

  相比于值类型简单的入栈出栈的资源分配使用方式,引用类型资源的分配使用是在较为复杂的CLR的管理下由GC执行垃圾回收机制。那有人就会问了:那既然值类型的性能高于引用类型,为什么不全都用值类型呢?或者换一种问法,我是不是可以在任何场合下肆无忌惮地使用值类型呢?

  那么试想一种情况:我自定义一个struct 类型作为一个方法的参数会发生什么呢?由于值类型在赋值的时候都是赋值传递的,那么每次调用都会发生全字段的赋值,这是不可接受的,这也是典型的值类型误用场景。而相对应地,引用类型在赋值的时候采用的是引用传递,传递的是对象的引用(指针),而指针变量保存的是一个指向堆内存中对象的地址,顶多只是一个int32的值,相较于一些复杂的结构类型来说,复制一个int的值比对结构的全字段进行赋值要简单的多。

 

  说了这么多还是不如画张图来的实在,下面两张图分别描述了在 调用参数为类类型(引用类型)函数 与 调用参数为结构类型(值类型)函数 时内存中的情况:

       

       

  通过上面两张图可以很直观的看出值传递和引用传递的区别:值传递是将值类型变量的值复制一个副本然后赋值给对应的函数参数,引用传递则是将对象的引用(指针)复制一个副本再赋值传递给对应的函数参数

 

   ok,也很简单嘛,值传递是赋值传递值类型数据本身,引用传递就是赋值传递对象的引用(指针)。理解了值传递和引用传递的原理,那么下面大家就带着对原理的掌握来尝试解释下面代码执行的结果,废话不多说,上代码:

复制代码
 1         class People  2         {  3             public string Name;  4             public string Info;  5         }  6  7         static void