CLR Via C# 3rd 阅读摘要 -- Chapter 5 - Primitive, Reference, and Value Types
Programming Language Primitive Types
1. 原生类型,FCL类型,CLS兼容性;(争论:在编写代码时究竟应该是优先使用keyword还是FCL类型?个人看法,只能是看情况;作者看法,FCL;C#语言规范,keyword)
作者认为优先使用FCL类型的理由:
- 开发人员经常会混淆,比如,很多开发者误认为int在64bit OS中是Int64,在32bit OS中是Int32,实际上C#中int=Int32;
- 不同语言中同名的原生类型却表达不同的含义,比如,C#中long=Int64,而C++中long=Int32;
- FCL中有些方法中会有类型名称作为方法名的一部分,比如,BinaryReader.ReadBoolean/ReadInt32/ReadSingle,这样使用keyword就会不自然;
- 在开发提供给其他客户使用的库中,要考虑哪些使用非C#的开发人员。
2. checked与unchecked,都是溢出给逼的。编译器开关/checked+;
作者建议: 如果可能,用有符号类型代替无符号类型(无符号类型是CLS所不兼容的);
- 显式的在那些不希望出现溢出的代码块上使用checked;
- 如果你认为溢出无所谓,那么也显式的使用unchecked;
- 如果不显式的使用checked或者unchecked,那么就应该假定溢出就要抛出异常(DEBUG:/checked+,RELEASE:/checked-)。
3. System.Decimal是比较特殊的,虽然语言把Decimal当成是原生类型,但是CLR可不这么认为,没有IL指令来维护Decimal值,所以checked或unchecked对Decimal是不起作用的,如果溢出了,那么总是抛出OverflowException。而且要注意的是,Decimal速度比较慢;
4. System.Numerics.BitInteger也比较特殊,它的内部使用了一个UInt32的数组来表示大整数,因此没有溢出的概念,除非内存分配光了,产生一个OutOfMemoryException。
Reference Types and Value Types
1. 引用类型:
- 内存必须分配在堆上;
- 每个分配在堆上的对象上面的成员都必须被初始化;
- 对象使用的其他字节都必须设置为0;
- 托管堆上分配的对象需要GC来回收。
2. struct定义的类型是值类型,值类型将在栈上分配;
3. CLR如何控制类型的字段在内存中的布局: LayoutKind.Auto是默认的(引用类型) ;而CLR对值类型的内存布局采用的是LayoutKind.Sequential。
Boxing and Unboxing Value Types
1. 装箱与拆箱的概念太一般了,总之值类型与引用类型之间互相转换就会引发装箱与拆箱行为,而这个过程对性能有负面影响(这点就不如Python了);
2. 对集合类型来说,泛型的容器应该是优先考虑的;
3. 编译器会悄悄的干一些装箱拆箱的勾当,而且干的很隐蔽,所以在使用值类型时要特别注意;
4. 虽然拆箱了的值类型没有类型对象指针,但是你仍然可以调用从类型中继承或覆盖的虚拟方法(比如Equals,GetHashCode,ToString),call(不会装箱)与callvirt(会装箱);
5. 为什么不能通过使用接口来改变装箱类型中的字段,技术上是可行的,但是不要这么干;
6. 对象相等:
- 理解ReferenceEquals()、静态Equals()、实例Equals()、operator==之间的关系;
- 相等必须是自反、对称、传递、一致性的;
- 不要重写static Object.ReferenceEquals()和static Equals();
- 总是为值类型重写instance Equals()和operator==(),operator!=();
- 为引用类型重些instance Equals();
- 实现System.IEquatable<T>接口的Equals方法,以及System.IComparable<T>.CompareTo方法,这些是类型安全的。
Object Hash Codes
1. 用于Hastable和Dictionary的类型需要生成一个散列值作为key。而这应该尽量避免,实在避免不了,应该遵循几个规则:
- 如果两个对象相等,那么必须生成相同的散列值;
- 任何对象A,A.GetHashCode()在生存期间散列值不变;
- 散列函数必须根据不同输入生成随机分布的整形值;
- 散列函数应该用到至少一个不可变的字段;
- 算法应该尽可能的快;
- 别存储散列值。
The Dynamic Primitive Type
1. dynamic就是System.Object,只是编译器会做隐式的类型转换;
2. dynamic和var是不同的:
- var只是一个一个语法糖,只能用于局部变量;
- dynamic可以用于局部变量,字段,参数;
- 可以把一个表达式转成一个var,而不能转成dynamic;
- 在声明时,必须初始化一个var变量,而不必初始化dynamic。
本章小结
本章主要讲述的是不同种类的类型(原生类型,引用类型、值类型,动态原生类型),以及这些类型的共性与特性。解释了装箱和拆箱的作用,哪些情况下会发生,如何来避免。还有需要特别注意的在override System.Object的方法(Equals, GetHashCode...)时的重要规则,还有dynamic与var之间的差别。