十三:引用类型和值类型
引用类型(reference type)
引用类型是从托管堆上分配的,C#的new操作符会返回对象的内存地址——也就是指向对象的内存地址,使用引用类型会对性能有一定的影响:
(1)内存必须从托管堆上分配
(2)堆一分配的每个对象都有一些额外的成员,而且这些成员必须初始化
(3)对象中的其它字节总是设为零
(4)从托管堆上分配一个对象,可能强制执行一次垃圾回收
值类型(value type)
值类型的实例通常是在线程堆栈上分配的,在代表值类型的一个变量中,并不包含一个指向实例的指针,变量中包含了实例本身的字段,在对值类型的实例操作时,不需要提领一个指针,值类型的实例不受垃圾回收器的制约。值类型缓解了托管堆上的压力,并减少一个应用程序在其生存期内要进行垃圾回收的次数。
任何称作“类”的类型都是引用类型,称为结构或者枚举的类型都是值类型。所有的结构都是从System.ValueType派生的,而System.ValueType又是从System.Object派生的,所有的枚举类型都是从System.Enum派生,而System.Enum又是从System.ValueType派生,所以可以说值类型是从System.ValueType派生。
以下代码和图演示了引用类型和值类型的区别:
//引用类型SomeRef
class SomeRef
{
public Int32 x;
}
//值类型SomeVal
struct SomeVal
{
public Int32 x;
}
static void ValueTypeDemo()
{
SomeRef r1 = new SomeRef();//在托管堆上分配
SomeVal v1 = new SomeVal();//在堆栈上分配
r1.x = 5; //提领指针
v1.x = 5; //在堆栈上实际修改
Console.WriteLine(r1.x); //显示5
Console.WriteLine(v1.x); //显示5
//以上代码为图的左边部分,以下代码为右边部分
SomeRef r2 = r1; //只复制引用()
SomeVal v2 = v1; //在堆栈上分配并复制成员
r1.x = 8; //修改r1.x和r2.x v1.x = 9; //只修改v1.x而不修改v2.x
Console.WriteLine(r1.x); //8
Console.WriteLine(r2.x); //8
Console.WriteLine(v1.x); //9
Console.WriteLine(v2.x); //5
}
上面
SomeVal v1 = new SomeVal();//在堆栈上分配
也可以写成
SomeVal v1;//在堆栈上分配
这一行生成的IL代码也公在线程堆栈上分配实例,并将字段初始化为0,但不使用new,C#编译器不会“认为”它已经初始化,使用它的字段时编译不通过。看以下代码,使用以上定义的两个类型:
SomeVal v1;
Console.WriteLine(v1.x);//编译不通过,使用了可能未赋值的字段x
v1.x = 1;
Console.WriteLine(v1.x);//输出1
SomeRef r1;
Console.WriteLine(r1.x);//编译不通过,使用了未赋值的局部变量r1
设计自己的类型时,是考虑使用值类型还是引用类型,某些时候,值类型比引用类型有更好的性能。以下几种情况应该考虑使用值类型:
(1)类型具有一个基元类型的行为,也就是这是一个非常简单的类型,其中没有成员会修改类型的任何字段。
(2)类型不需要从其它任何类型继承也不会派生出其它任何类型
(3)类型的实例较小,约为16字节或更小
(4)类型的实例较大,大于16字节,但不作为方法传递,也不作为方法的返回类型使用。