6

看到值类型的新实例在每次作为参数传递时都会创建,我开始考虑使用reforout关键字可以显着提高性能的场景。

过了一会儿,我突然想到,虽然我看到使用值类型的不足之处,但我不知道有什么好处。
所以我的问题相当直截了当——拥有值类型的目的是什么?通过复制结构而不是仅仅创建对它的新引用,我们可以获得什么?

在我看来,只有像 Java 这样的引用类型会容易得多。

编辑:为了澄清这一点,我指的不是小于 8 字节(引用的最大大小)的值类型,而是 8 字节或更多的值类型。

例如 -Rectangle包含四个int值的结构。

4

6 回答 6

14
  • 一字节值类型的实例占用一个字节。引用类型占用引用加上同步块和虚函数表的空间和...

  • 要复制引用,请复制四(或八)字节引用。要复制一个四字节整数,请复制一个四字节整数。复制小值类型并不比复制引用更昂贵。

  • 垃圾收集器根本不需要检查不包含引用的值类型。垃圾收集器必须跟踪每个引用。

于 2012-02-18T23:37:38.450 回答
3

“创建参考”不是问题。这只是 32/64 位的副本。创建对象是昂贵的。实际上创建对象很便宜,但收集它却不是。

当值类型很小且经常被丢弃时,它们对性能有好处。它们可以非常有效地用于大型阵列。结构没有对象头。还有很多其他的性能差异。

编辑:Eric Lippert 在评论中举了一个很好的例子:“如果它们是值类型,一百万字节的数组占用多少字节?如果它们是引用类型,它占用多少?”

我会回答:如果 struct packing 设置为 1,这样的数组将占用 100 万个 16 个字节(在 32 位系统上)。使用引用类型将需要:

array, object header: 12
array, length: 4
array, data: 4*(1 million) = 4m
1 million objects, headers = 12 * (1 million)
1 million objects, data padded to 4 bytes: 4 * (1 million)

这就是为什么在大型数组中使用值类型可能是一个好主意。

于 2012-02-18T23:34:35.533 回答
3

值类型通常比引用类型更高效:

  • 引用类型在取消引用时会为引用和性能消耗额外的内存

  • 值类型不需要额外的垃圾回收。它与它所在的实例一起收集垃圾。方法中的局部变量在方法离开时被清理。

  • 值类型数组与缓存结合起来很有效。(想想一个 int 数组与一个 type 实例数组相比Integer

于 2012-02-18T23:39:56.653 回答
2

如果您的数据很小(<16 字节),您有很多实例和/或您对它们进行了很多操作,特别是传递给函数,则收益是可见的。这是因为与创建小值类型实例相比,创建对象相对昂贵。正如其他人指出的那样,需要收集物品,而且成本更高。另外,非常小的值类型比它们的引用类型等价物占用更少的内存。

.NET 中非原始值类型的示例是点结构 (System.Drawing)。

于 2012-02-18T23:36:18.397 回答
1

每个变量都有一个生命周期。但并非每个变量都需要灵活的变量来执行高而不是在堆中管理。

值类型 (Struct) 包含在堆栈中分配或在结构中内联分配的数据。引用类型(类)存储对值的内存地址的引用,并在堆上分配。

拥有值类型的目的是什么?值类型对于处理简单数据非常有效,(应该用来表示不可变类型来表示值)

值类型对象不能在垃圾回收堆上分配,代表对象的变量不包含指向对象的指针;变量包含对象本身。

通过复制结构而不是仅仅创建对它的新引用,我们可以获得什么?

如果复制结构,C# 会创建对象的新副本并将对象的副本分配给单独的结构实例。但是,如果您复制一个类,C# 会创建一个对该对象的引用的新副本,并将该引用的副本分配给单独的类实例。结构不能有析构函数,但类可以有析构函数。

于 2012-02-19T00:05:46.063 回答
1

像这样的值类型的一个主要优点Rectangle是,如果一个有n 个type 的存储位置Rectangle,则可以确定一个有n 个不同的 type 实例RectangleMyArray如果有一个类型为的数组Rectangle,长度至少为 2,则类似的语句MyArray[0] = MyArray[1]会将 的字段复制到 的字段中MyArray[1]MyArray[0]但它们将继续引用不同的Rectangle实例。如果一个然后执行MyArray[0].X += 4将修改X一个实例的字段的语句行,而不修改X任何其他数组槽或Rectangle实例的值。请注意,顺便说一下,创建数组会立即用可写Rectangle实例填充它。

想象一下,如果Rectangle是一个可变的类类型。创建一个可变Rectangle实例数组需要一个第一维数组,然后为数组中的每个元素分配一个新Rectangle实例。如果想将一个矩形实例的值复制到另一个,则必须说类似MyArray[0].CopyValuesFrom(MyArray[1])[当然,如果MyArray[0]没有填充对新实例的引用,这将失败)。要是不小心说了MyArray[0] = MyArray[1],写到MyArray[0].X也会有影响MyArray[1].X。讨厌的东西。

重要的是要注意,在 C# 和 vb.net 中有几个地方,编译器会隐式复制一个值类型,然后对一个副本进行操作,就好像它是原始的一样。这是一个非常不幸的语言设计,并促使一些人提出值类型应该是不可变的命题(因为大多数涉及隐式复制的情况只会导致可变值类型的问题)。当编译器非常不善于警告语义上可疑的副本会产生破坏行为的情况时,这样的概念可能是合理的。不过,今天应该认为它已经过时了,因为任何体面的现代编译器都会在大多数隐式复制会产生破坏语义的场景中标记错误,包括所有结构仅通过构造函数、属性设置器、或对公共可变字段的外部分配。像这样的声明MyArray[0].X += 5比 . 更具可读性MyArray[0] = new Rectangle(MyArray[0].X + 5, MyArray[0].Y, MyArray[0].Width, MyArray[0].Height)

于 2012-06-05T22:33:42.493 回答