条目45 尽量减少装箱和拆箱
.NET Framework用装箱和拆箱在值类型和引用类型之间架起了桥梁。
装箱将把一个值类型放在一个未确定类型的引用对象中,让这个值类型也能在需要引用类型的地方使用,而拆箱则是指从箱中获取出其中值类型的副本。
装箱和拆箱都是较为影响性能的操作,有时还会创建对象的临时副本,进而导致一些难以发现的Bug。你应尽量地避免装箱和拆箱操作。
状态会把值类型转换为引用类型。这个新的引用类型(即箱子)将分配于堆上,该值类型的一个副本会存放在引用类型的内部。
箱子包含了值类型的副本,并提供了值类型公有接口的功能。若是需要从箱子中获取数据,那么将创建其中值类型的一个副本并返回。装箱和拆箱的关键之处:装箱时存放的是值类型的副本,获取箱中数据时返回的是值类型的另一个副本。
泛型能让你用泛型类或泛型方法来避免装箱/拆箱操作。
.NET Framework中很多方法的参数类型仍为System.Object,因此这些API还是需要拆行装箱和拆箱操作。这个过程是自动的,若是将值类型传递到需要引用类型的地方,那么编译器将自动生成装箱/拆箱的指令。此外通过接口指针使用值类型时,也会自动发生装箱/拆箱操作。
等价于:
在WriteLine中:
你应在将值类型传递给WriteLine之前,手工将其转换成字符串:
由此给出避免装箱的第一条规则:小心到System.Object的隐士转换。若可以避免的话,不要用值类型代替System.Object。
在使用集合类型时,尽量使用泛型集合。
值类型可以转换为System.Object或其他接口的引用。转换过程是隐式的,也就更难以发现。装箱和拆箱的操作会在你无意识中创建副本,这往往会导致Bug。以多态的形式处理值对象也会带来性能上的损失。小心那些将把值类型转换成System.Object或接口类型的代码。例如在集合中存放值类型、调用System.Object中的方法、转型为System.Object等。应该尽量避免这些操作。
条目46 为应用程序创建专门的异常类
异常是一种报告错误的机制,在这种机制中,错误的抛出位置和处理位置可能相距甚远。因此,有关错误的所有信息都必须包含在异常对象中。此外,还可能需要将低级别的错误转换成应用程序领域内特定的异常,同时保留原始错误的所有信息。
第一步是了解何时以及为何创建新的异常类,并考虑应该如何组织完备的异常层次结构。开发者使用你的类库编写catch子句时,会根据异常运行时类型的不同有区别地执行处理逻辑。
首先,异常并不适合于你所遇到的每一个问题,虽然没有什么明确的指导意见,但建议对于那些如若不及时报告并处理会引发长久问题的错误,应该抛出异常。
齐次,编写throw语句并不代表就要在此处创建一个全新的异常类。在将低层次的异常转换成更高级别的、包含更多上下文信息的异常。这种处理方式称为异常转换。
要再次强调的是,提供不同异常类的原因是,让捕获异常的代码能根据不同的错误条件给出不同的处理方式。查看哪些可能会进行同类型恢复操作的异常条件,然后按照这样的标准创建专门的异常类。
集合初始化时应指定初始值大小且需要向4对齐
以List为例。
这是因为集合类型的对象在触发扩容机制时,会生成一个更大空间的集合类型来存储(原始大小*2),且因为默认大小为4,所以在指定大小时向4对齐,保证指定的大小是4的倍数且向上取整到刚好放得下现有的集合内项的数目。这是因为如果不是4的倍数的时候,下一次再有类型去申请内存空间的时候,它无法保证能申请到自己想要的大小,就会导致大量的内存碎片化,比如剩下3个大小的空间没有对象能申请到。