在类和结构之间选择

注意

此内容根据 Pearson Education, Inc. 许可转载自《框架设计指南:可重用 .NET 库的约定、习语和模式第二版》。 该版本于 2008 年出版,并在此后于第三版对该书进行了全面修订。 此页上的一些信息可能已过时。

每个框架设计人员都会面临一个基本设计决策:是将类型设计为类(引用类型)还是结构(值类型)。 在做出选择时,准确理解引用类型和值类型的行为差异至关重要。

我们要考虑的引用类型和值类型的第一个区别是:引用类型是在堆上分配的,会进行垃圾回收;而值类型要么在堆栈分配并在堆栈展开时解除分配,要么内联在包含类型中分配并在其包含类型被解除分配时解除分配。 因此,值类型的分配和解除分配通常比引用类型的分配和解除分配开销更低。

第二个区别是:引用类型的数组不是内联分配的,也就是说数组的元素只是一些引用,指向那些位于堆中的引用类型的实例。 值类型数组的分配是内联的,这意味着数组元素是值类型的真正实例。 因此,分配和解除分配值类型数组通常要比分配和解除分配引用类型数组的开销低得多。 此外,大多数情况下,值类型数组具有更好的引用局部性。

第三个区别与内存使用率相关。 值类型在被强制转换为引用类型或它们实现的接口之一时,要执行装箱操作。 它们在被强制转换回值类型时,将执行取消装箱操作。 因为箱子是在堆上分配的对象,会进行垃圾回收,所以装箱和取消装箱操作太多会对堆、垃圾回收器,并最终对应用程序的性能产生负面影响。 相比之下,在对引用类型执行强制转换操作时,不会发生装箱操作。 (有关详细信息,请参阅装箱和取消装箱)。

第四个区别是引用类型的赋值复制引用,而值类型的赋值会复制整个值。 因此大型引用类型赋值的开销比大型值类型赋值的开销要低。

最后,引用类型是按引用传递的,而值类型按值传递。 改变引用类型的一个实例会影响到所有指向该实例的引用。 值类型实例在按值传递时被复制。 当值类型的一个实例被改变时,显然不会影响它的任何副本。 由于副本不是由用户显式地创建的,而是在传递参数或返回值时隐式创建的,因此可以更改的值类型会把许多用户搞糊涂。 因此,值类型是不可变的。

一般说来,框架中的大多数类型应该是类。 但在某些情况下,值类型的特征使得其更适合使用结构。

✔️ 如果类型的实例比较小并且通常生存期较短或者常嵌入在其他对象中,则考虑定义结构而不是类。

❌ 避免定义结构,除非具有所有以下特征:

  • 它逻辑上表示单个值,类似于基元类型(intdouble 等等)。

  • 它的实例大小小于 16 字节。

  • 它是不可变的。

  • 它不必频繁装箱。

在所有其他情况下,都应将类型定义为类。

Portions © 2005, 2009 Microsoft Corporation 版权所有。 保留所有权利。

在 Pearson Education, Inc. 授权下,由 Addison-Wesley Professional 作为 Microsoft Windows 开发系列的一部分再版自 Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition(Framework 设计准则:可重用 .NET 库的约定、惯例和模式第 2 版),由 Krzysztof Cwalina 和 Brad Abrams 发布于 2008 年 10 月 22 日。

请参阅