第五章 基元类型、引用类型和值类型
- 基元类型:int、float等编译器直接支持(不需要显式new)的类型
- c#中,不论32/64操作系统,int总是Int32,long总是Int64
- 基元类型之间可以显示或隐式的类型转换(比如int值直接赋值给long)
- check和uncheck操作:基元类型的算术运算,可能出现溢出
- 编译时,使用/checked命令来决定溢出时像c语言一样回滚还是像basic一样抛出异常
- 使用checked/unchecked操作符或语句指定
- 作者推荐使用无符号类型,来让编译器检测更多溢出错误,以及减少强制类型转换的次数
- 使用引用类型时需要注意性能问题:
- 内存必须从托管堆分配
- 堆上分配的每个对象都有额外成员,额外成员都要初始化
- 对象中的其它字节(字段)总是设为0
- 从托管堆中分配对象时,可能强行执行一次垃圾回收
- 值类型一般在线程栈上分配,包含实例本身的字段,操作值类型不需要提领指针,不受垃圾回收器控制
- 类都是引用类型;结构和枚举都是值类型(所有结构都派生自ValueType,ValueType派生自Object,所有枚举派生自Enum,Enum派生自ValueType)
- 但是对于非托管C/C++,静态内存分配在编译时分配到栈上,动态内存分配使用new运算符分配在堆上,与它是引用类型还是值类型无关,和声明这个类型的代码无关
- 值类型可以实现接口,但是不能作为基类
- 就算是使用new操作符来实例化值类型,它仍然会在栈上分配,但是会被认为已经被初始化为0(不会报错:可能使用了未被赋值的字段)
- 要把类型声明成值类型,应当满足以下条件(为什么建议用class而不是struct)
- 类型有基元类型的行为,没有成员会修改类型的实例字段,对值类型,作者建议把全部成员标记为readonly
- 类型不从其它类型继承,也不派生出其它类型
- 类型的实例较小(<16字节)
- 类型的实例较大,但是不作为方法实参传递,也不从方法返回
- 值类型和引用类型的区别
- 值类型有已装箱和未装箱两种形式,引用类型只有已装箱形式
- ValueType重写了Equals(所有字段相等即返回true)和GetHashCode(考虑所有字段的值)
- 值类型的所有方法都不能是抽象的(因为不能派生出其它类)
- 引用类型初建变量时初始化为null;值类型所有字段初始化为0,访问未初始化的值类型不能抛出null异常
- 值类型赋值会逐字段复制;引用类型只复制地址
- 一旦定义了值类型的方法不再活动,该值类型实例会被立刻释放,不会等待垃圾回收
- 值类型转换成引用类型(装箱机制)struct->object
- 在托管堆分配内存(所有字段的内存+额外成员)
- 值类型字段值复制到新分配的内存
- 返回对象地址(引用)
- 拆箱机制
- 如果引用为null,抛出异常;如果引用对象不是所需类型的已装箱实例,抛出异常
- 获取对象各字段地址(这个过程是拆箱)
- 将字段包含的值从堆复制到栈上的值类型实例中(这个过程是字段复制)
- 现在使用的泛型集合类,操作集合时不再需要装箱/拆箱(List<T>等)
- 未装箱值类型可以调用继承或重写的虚方法;如果要调用需方法在基类的实现,需要装箱;如果要调用非虚、继承的方法时,需要装箱;转换成某个接口时,需要装箱
- C# 不允许更改已装箱值类型中的字段
- 一个值类型p已装箱成object o, 此时不允许使用o更改字段(首先o拆箱成值类型,储存在【临时变量】中,只会修改临时变量的值)
- 使用接口可以修改值类型的字段(值类型p装箱成object o,o转换成接口时,不需要再装箱,就可以更改字段的值)
- 对象的相等性和同一性
- Object的Equals实现的是同一性,即this和实参变量是不是引用同一个对象
- 由于类型可以重载Equals,所以不能用它测试同一性,而应该使用ReferenceEquals
- 值类型重载了Equals方法,执行的是相等性检查(每个实例字段的值相等)
- 重写Equals方法时要注意1、实现IEquatable<T>接口的Equals方法2、重载==、!=运算符
- 对象哈希码GetHashCode
- 当重写Equals方法时,应同时重写GetHashCode方法(在哈希表、字典等集合的实现中,要求两个变量有相同的哈希码才可以相等,所以必须确保相等性算法与哈希码算法一致)
- 实现哈希算法时应注意
- 有良好的随机分布
- 可以调用基类的GetHashCode,但不要使用Object或ValueType的GetHashCode实现(两者性能差)
- 算法至少使用一个实例字段
- 算法使用的字段在对象生存期值不改变
- 执行速度快
- 包含相同值的不同对象返回相同的哈希码
- dynamic基元类型
- c#可以将表达式的类型标记为dynamic,可以将表达式的结果存在标记为dynamic的变量中;
- 编译器会自动将dynamic转换为Object
- 实参可以是dynamic;任何表达式可以隐式转换为dynamic;从dynamic转换为其它类型时CLR将验证对象类型是否兼容
- 使用dynamic会造成额外的性能开销(考虑使用反射方法或进行手动类型转换)