一、值类型 和 引用类型
概念:值类型直接存储其值,而引用类型存储对其值的引用。
值类型 和 引用类型是以它们在计算机内存中是如何被分配的来划分的。值类型包括 结构和枚举,引用类型包括类、接口、委托 等。还有一种特殊的值类型,称为简单类型(Simple Type),比如 byte,int等,这些简单类型实际上是FCL类库类型的别名,比如声明一个int类型,实际上是声明一个System.Int32结构类型。因此,在Int32类型中定义的操作,都可以应用在int类型上,比如 “123.Equals(2)”。
所有的 值类型 都隐式地继承自 System.ValueType类型(注意System.ValueType本身是一个类类型),System.ValueType和所有的引用类型都继承自 System.Object基类。你不能显示地让结构继承一个类,因为C#不支持多重继承,而结构已经隐式继承自ValueType。
常用的值类型:byte,short,int,long,float,double,decimal,char,bool,enum 和struct
常用的引用类型:string 和 class
下面再通过一段代码来认识引用类型和值类型的区别:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace ConsoleApp1{ //引用类型 class RefData { public Int32 a; } //值类型 struct ValData { public Int32 a; } class Program { static void Main(string[] args) { RefData r1 = new RefData(); //在堆上分配 ValData v1 = new ValData(); //在栈上分配 r1.a = 5; //在托管堆上修改 v1.a = 5; //在栈上修改 Console.WriteLine(r1.a); //显示“5” Console.WriteLine(v1.a); //也显示“5” RefData r2 = r1; //只复制引用(指针) ValData v2 = v1; //在栈上分配并复制成员 r1.a = 8; //r1.a和r2.a都会更改 v1.a = 9; //v1.a会更改,但v2.a不变 Console.WriteLine(r1.a); //显示“8” Console.WriteLine(r2.a); //显示“8” Console.WriteLine(v1.a); //显示“9” Console.WriteLine(v2.a); //显示“5” } }}
二、值类型和引用类型的使用优化
值类型通常被人们称为轻量级的类型,因为在大多数情况下,值类型的的实例都分配在线程栈中,因此它不受垃圾回收的控制,缓解了托管堆中的压力,减少了应用程序的垃圾回收的次数,提高性能。
所有的引用类型的实例都分配在托管堆上,c#中new操作符会返回一个内存地址指向当前的对象。所以当你在创建个一个引用类型实例的时候,你必须要考虑以下问题:
- 内存是在托管堆上分配的
- 在分配每一个对象时都会包含一些额外的成员(类型对象指针,同步块索引),这些成员必须初始化
- 对象中的其他字节总是设为零
- 在分配对象时,可能会进行一次垃圾回收操作(如果托管堆上的内存不够分配一次对象时)
虽然值类型是一个轻量级的类型,但是如果大量的使用值类型的话,也会有损应用程序的性能(例如装箱和拆箱操作,传递实例较大的值类型,或者返回较大的值类型实例)。由于值类型实例的值是自己本身,而引用类型的实例的值是一个引用,所以如果将一个值类型的变量赋值给另一个值类型的变量,会执行一次逐字段的复制,将引用类型的变量赋值给另一个引用类型的变量时,只需要复制内存地址,在对大对象进行赋值时要避免使用值类型。所以要优化性能要合理的使用这两种类型。
三、初学者常见误区
1、引用类型分配在托管堆上,值类型分配在栈上:其实值类型也可能分配在堆上。因为变量的值在它声明的位置存储的,所以假如某一个引用类型中有一个值类型的变量, 那么该变量的值总是和该引用类型的对象的其它数据在一起,也就是分配在堆上。(只有局部变量(方法内部声明的变量)和方法的参数在栈上)
2、结构是轻量级的类:这种错误的信息主要是因为有人认为值类型不应该有方法或者其它有意义的行为-它们应该作为简单的数据转移来使用,所以很多人分不清DateTime到底是值类型还是引用类型。
3、对象在c#中默认的是用引用传递的:其实在调用方法的时候,参数值(对象的一个引用)是以传值得方式传递的,如果你想以引用方式传递的话,可以使用ref或者out关键字。