我刚刚参加了一次IKM C#测试。其中一个问题是:
以下哪个能够提高C#程序的性能?
- A. 使用装箱
- B. 使用拆箱
- C. 不使用常量
- D. 使用空析构函数
- E. 使用值类型而不是引用类型
最终我跳过了这个问题,我唯一能看到的可能答案是E。在某些情况下,值类型可以提供更好的性能(对于小类型:不需要解引用且不在托管堆上[假设不是引用类型的成员]),但并非总是如此。
我刚刚参加了一次IKM C#测试。其中一个问题是:
以下哪个能够提高C#程序的性能?
最终我跳过了这个问题,我唯一能看到的可能答案是E。在某些情况下,值类型可以提供更好的性能(对于小类型:不需要解引用且不在托管堆上[假设不是引用类型的成员]),但并非总是如此。
关于错误答案的一些说明:
装箱转换是将值类型值转换为引用类型值,即对象。它涉及从垃圾回收堆中分配内存,创建一个对象头,该对象头将对象标识为值类型所属的类型,并将值类型值位复制到对象中。这是创建类型系统幻觉的转换,使得值类型派生自System.ValueType和System.Object。在.NET 1.x程序中广泛使用装箱转换,因为它仅支持System.Collections中的类作为集合类型,这些集合类型的元素是Object。.NET Generics在2.0中添加使得这些类立即过时,因为它允许创建System. Collections.Generic中的类,可以存储值而不必进行装箱。所以不对。
拆箱是相反的转换,即从装箱对象值返回值类型值。与装箱不太昂贵相比,它仅涉及检查装箱对象的类型是否为预期类型并复制值类型值位。它需要在C#中进行强制转换,如果装箱值类型不匹配,则容易引发异常。与之前的一样,也不对。
带有const关键字的标识符是直接编译到编译器生成的IL中的文字值。另一种是readonly关键字,它需要访问内存以加载值,因此始终较慢。const标识符应始终为private或internal,公共常量容易在部署修复程序时破坏程序,因为您更改值但未重新编译使用常量的程序集。这些程序集仍将使用旧的常量值,因为它已编译到其代码中。这是readonly值不会出现的问题,所以也不对。
析构函数(也称为终结器)会大幅增加对象的成本。垃圾回收器确保在对象被垃圾回收时调用终结器。但为此,它必须单独跟踪对象,这种对象被放置在终结器队列上,等待终结器线程执行终结器。该对象实际上直到下一次 GC 才真正被销毁。你几乎总是需要为这样的对象实现 IDisposable 类,这样程序可以早期调用终结器的职责,而不是让运行时自动完成。在 Dispose() 方法中调用 GC.SuppressFinalize()。最糟糕的情况就是一个没有任何作用的终结器,所以不要留下它。
.NET 中存在值类型的原因是它们比引用类型更高效。它们的值占用比引用类型对象少得多的内存,并且可以存储在 CPU 寄存器和 CPU 堆栈中,这些内存位置在处理器设计中高度优化。他们对语言设计构成了负担,因为将它们抽象为对象是一种漏洞的抽象,会吞噬无法检测到的 CPU 周期,特别是当你尝试变异结构体时,这是一种困难的类型,容易破坏程序。但为了避免像 Smalltalk 这样的超纯语言所遭受的性能损失,使用值类型是很重要的。Smalltalk 是一种先锋 OOP 语言,在其中每个值都是一个对象,并影响了大量后续的 OOP 语言。但由于其性能差且没有明确的路径让硬件工程师将其快速地运行,它很少被实际使用。与 C# 不同,C# 不会抽象处理器设计,因此使用 C# 可以避免这种问题。