CLR via C# 引用类型和值类型

CLR支持两种类型:引用类型和值类型。虽然FCL的大多数类型都是引用类型,但程序员用得最多的还是值类型。

引用类型总是从托管堆分配,C#的new操作符返回对象内存地址---即指向对象数据的内存地址。

//--

使用引用类型必须留意性能问题。首先要认清楚以下四个事实:

1.内存必须从托管堆分配。

2.堆上分配的每个对象都有一些额外成员,这些成员必须初始化。

3.对象中的其他字节(为字段而设)总是设为零。

4.从托管堆分配对象时,可能强制执行一次垃圾回收。

//--

如果所有类型都是引用类型,应用程序的性能将显著下降。

为了提升简单和常用的类型的性能,CLR提供了名为“值类型”的轻量级类型。

值类型的实例一般在线程栈上分配(虽然可以作为字段嵌入引用类型的对象中)。

在代表值类型实例的变量中不包含指向实例的指针。相反,变量中包含了实例本身的字段。由于变量已包含了实例的字段,所以操作实例中的字段不需要提领指针。

值类型的实例不受垃圾回收器的控制。因此,值类型的使用缓解了托管堆的压力,并减少了应用程序生存期内的垃圾回收次数。

//--

任何称为“类”的类型都是引用类型。相反,所有值类型都称为结构或枚举。

所有结构都是抽象类型System.ValueType的直接派生类。System.ValueType本身又直接从System.Object派生。

根据定义,所有值类型都必须从System.ValueType派生。所有枚举都从System.Enum抽象类型派生,后者又从System.ValueType派生。

//--

虽然不能在定义值类型时为它选择基类型,但是,值类型可以实现一个或多个接口。

除此之外,所有值类型都隐式密封,目的是防止将值类型用作其他引用类型或值类型的基类型。

//--

在托管代码中,要由定义类型的开发人员决定在什么地方分配类型的实例,使用类型的人对此并没有控制权。

//--

 上面代码有这样一行:

因为这行代码的写法,似乎是要在托管堆上分配一个SomeVal实例。但是C#编译器知道SomeVal是值类型,所以会生成正确的 IL 代码,在线程栈上分配一个SomeVal实例。C#还会确保值类型中的所有字段都初始化为零。

上述代码还可以这样写:

 这一行生成的 IL 代码也会在线程栈上分配实例,并将字段初始化为零。唯一的区别在于,如果使用new操作符,C#会认为实例已初始化。一下代码更清楚地进行了说明:

//-- 

设计自己的类型时,要仔细考虑类型是否应该定义成值类型而不是引用类型。值类型有时能提供更好的性能。

具体地说,除非满足一下全部条件,否则不应该将类型声明为值类型:

1.类型具有基元类型的行为。也就是说,是十分简单的类型,没有成员会修改类型的任何实例字段。如果类型没有提供会更改其字段的成员,就说该类型是不可变(immutable)类型。事实上,对于许多值类型,我们建议将全部字段标记为readonly。

2.类型不需要从其他任何类型继承。

3.类型也不派生出其他任何类型。

类型实例大小也应在考虑之列,因为实参默认以传值方式传递,造成对值类型实例中的字段进行复制,对性能造成损害。同样的,被定义为返回一个值类型的方法在返回时,实例中的字段会复制到调用者分配的内存中,对性能造成损害。所以,要将类型声明为值类型,除了要满足以上全部条件,还必须满足一下任意条件:

1.类型的实例较小(16字节或更小)。

2.类型的实例较大(大于16字节),弹不作为方法实参传递,也不从方法返回。

//--

值类型的优势是不作为对象在托管堆上分配。当然,与引用类型相比,值类型也存在自身的一些局限。

下面列出了值类型和引用类型的一些区别:

1.值类型对象有两种表示形式:未装箱和已装箱。相反,引用类型总是处于已装箱形式。

2.值类型从System.ValueType派生。该类型提供了与System.Object相同的方法。但是System.ValueType重写了Equals方法,能在两个对象的字段值完全匹配的前提下返回true。此外,System.ValueType重写了GetHashCode方法。生成哈希码时,这个重写方法所用的算法会将对象的实例字段中的值考虑在内。由于这个默认实现存在性能问题,所以定义自己的值类型时应重写Equals和GetHashCode方法,并提供它们的显式实现。

3.由于不能将值类型作为基类型来定义新的值类型或者新的引用类型,所以不应在值类型中引入任何新的虚方法。所有方法都不能是抽象的,所有方法都隐式密封(不可重写)。

4.引用类型的变量包含堆中对象的地址。引用类型的变量创建时默认初始化为null,表明当前不指向有效对象。相反,值类型的变量总是包含其基础类型的一个值,而且值类型的所有成员都初始化为0。

5.将值类型变量赋给另一个值类型变量,会执行逐字段的复制。将引用类型的变量赋给另一个引用类型的变量只复制内存地址。

6.基于上一条,两个或多个引用类型变量能引用堆中同一个对象,所以一个变量执行的操作可能影响到另一个变量引用的对象。相反,值类型变量自成一体,对值类型变量执行的操作不可能影响另一个值类型变量。

7.由于未装箱的值类型不再堆上分配,一旦定义了该类型的一个实例的方法不再活动,为它们分配的存储就会被释放,而不是等着进行垃圾回收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值