《Microsoft .NET框架程序设计(修订版)》 第五章

第五章 基元类型,引用类型与值类型

 

Ø  5.1 基元类型

编译器直接支持的数据类型成为基元类型。基元类型和.NET框架类库中的类型有直接的映射关系。

如果两个类型之间的转换是“安全”的,那么C#允许在它们之间进行隐式转换,这里安全的意思是转换过程中不会造成数据损失。如果转型存在潜在的“不安全”,C#将要求显示转型。对于值类型,“不安全”的转换意味着我们可能会因此丢失精度或者数量级。

不同的编译器可能产生不同的代码来处理这些转型操作,C#总是会舍弃小数部分。

 

Ø  5.1.1 Checked与UnChecked基元类型操作

Ckecked与Unchecked只对基元类型进行溢出检查,而decimal不是JIT汇编器的基元类型,所以checked和unchecked语句,以及相关的编译器命令行开关对它没有任何影响。

 

Ø  5.2 引用类型与值类型

引用类型总是从托管堆上分配,C#的new操作符返回的是对象位于托管堆中的内存地址——该内存地址指向对象占用的数据位。

值类型实例通常分配在线程的堆栈上。表示值类型实例的变量不包含指向实例的指针——变量本身即包含了实例所有的字段。因为变量本身包含所有的实例的字段,所以操作实例时也就无需在解释指针引用。值类型实例不受垃圾收集器的控制,因此也减少了托管堆的压力,以及应用程序在整个生存期中需要垃圾回收的次数。

所有的值类型都直接继承自System.ValueType类型。

所有的枚举类型都继承自System.Enum,System.Enum本身又继承自System.ValueType

CLR不允许一个值类型被用作任何其他引用类型或值类型的基类型。

值类型对象有两种表示:一种是未装箱形式,一种是装箱形式。引用类型总是装箱形式。

我们不允许在值类型中引入任何新的虚方法,值类型中更不可以有任何的抽象方法,所有的方法都隐含为密封方法。

引用类型的变量包含着对象在托管堆中的内存地址,当一个引用类型变量被创建时,它被初始化为null,表示该引用类型变量目前没有指向一个有效的对象。值类型变量总是包含一个符合它的类型的值,并且所有的字段都被初始化为0。当访问一个值类型时,不可能产生NullReferenceException异常。

当将一个值类型赋值给另一个值类型时,会进行一个字段对字段的拷贝,但当一个引用类型变量赋值给另一个引用类型变量时,只会拷贝内存地址。

两个或多个引用类型变量可以指向托管堆中的同一个对象,这样对一个变量的操作将会影响到其他变量引用的对象。每个值类型变量都有一份自己的“对象”数据拷贝,对一个值类型变量的操作不可能影响到另一个。当然如果值类型中定义有引用类型字段,对一个值类型变量的操作还是有可能影响到另一个值类型的变量的。

因为未装箱值类型没有分配在托管堆上,所以一旦定义该类型实例的方法不再处于活动状态,为它们分配的存储空间就会立即释放,这意味着值类型实例在内存被回收时不可能收到任何通知,因此许多编译器包括C#和VB都不允许我们在值类型中定义Finalize方法。

Ø  5.3 值类型的装箱与拆箱

.NET框架中,一种称作装箱的机制用来将一个值类型转换为一个引用类型。装箱操作通常由以下几步组成:

1.  从托管堆中为新生成的引用类型对象分配内存

2.  将值类型实例的字段拷贝到托管堆上新分配对象的内存中

3.  返回托管堆中新分配对象的地址。该地址就是一个指向对象的引用。值类型实例也就变成了一个引用类型对象。

经过装箱的值类型实例的生存期超过了未装箱得值类型实例的生存期。

拆箱操作的代价要比装箱操作小得多。拆箱操作是获取指向对象中包含的值类型部分(数据字段)的指针而已,它不会像装箱操作那样涉及到任何内存字节的拷贝。然而,紧接着拆箱之后典型的操作往往就是字段拷贝,这两个操作合起来与装箱操作正好成为真正的互反操作。

在C#中,拆箱操作总是紧跟着一个字段拷贝工作。一些语言如托管扩展C++允许我们对一个已装箱的值类型实例执行拆箱操作时,不必拷贝其中的字段。

因为未装箱值类型没有SyncBlockIndex,所以我们不可能利用System.Threading.Monitor类型来同步多个线程对它们的访问。因为未装箱值类型没有方向表指针,我们也不可能通过值类型的未装箱实例来调用其上继承而来的虚方法。另外,将一个未装箱的值类型实例转换为一个该类型实现的接口类型也需要对该类型进行装箱,因为接口类型总是引用类型。

System.Object提供一个Equals虚方法跟一个静态的ReferenceEquals方法来检查同一性。

重写Equals要符合4个特征:自反的、对称的、可传递的、一致的。

重写Equals方法需要让类型实现System.IEqualable<T>接口,重载==和!=操作符方法。

Ø 5.4 对象哈希码

如果你定义的一个类型重写了Equals方法,那么还应重写GetHashCode方法。

在一个集合中添加一个键/值对时,首先会获取键对象的一个哈希码。这个哈希码指出键/值对应该存储到哪一个哈希桶中。

千万不要对哈希码进行持久化,因为哈希码很容易改变。

5.5 dynamic基元类型

不要混淆dynamic和var,用var声明一个局部变量只是一种简化语法,它要求编译器根据一个表达式推断具体的数据类型。var关键字只能声明方法内部的局部变量,而dynamic关键字可以用于局部变量,字段和参数。表达式不能转型为var,但能转型为dynamic。必须显式初始化var声明的变量,但无需初始化用dynamic声明的变量。

Vsual Studio无法提供任何“智能感知”支持来帮助你写针对dynamic表达式的代码。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值