价值类型优于参考类型的好处?

时间:2012-02-18 23:31:06

标签: c# pass-by-reference pass-by-value value-type reference-type

看到每次作为参数传递时都会创建值类型的新实例,我开始考虑使用refout关键字可以显示出显着的性能提升的情况。

过了一会儿它打击了我,虽然我看到使用价值类型的不足,但我不知道有什么好处 所以我的问题很简单 - 拥有价值类型的目的是什么?我们通过复制结构而不是仅仅创建一个新的引用来获得什么?

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

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

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

6 个答案:

答案 0 :(得分:13)

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

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

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

答案 1 :(得分:3)

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

值很小,经常被丢弃,对性能有好处。它们可以非常有效地用于大型阵列中。 struct没有对象头。 有很多其他性能差异。

编辑: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)

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

答案 2 :(得分:3)

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

  • 在解除引用

  • 时,引用类型会为参考和性能花费额外的内存
  • 值类型不需要额外的垃圾回收。它将垃圾与它所在的实例一起收集。方法中的局部变量在方法离开时被清理。

  • 值类型数组与缓存结合使用效率很高。 (想想与Integer类型的实例数组相比较的一组整数)

答案 3 :(得分:2)

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

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

答案 4 :(得分:1)

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

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

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

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

我们通过复制结构而不是仅仅创建一个新的引用来获得什么?

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

答案 5 :(得分:1)

Rectangle等值类型的一个主要优点是,如果一个 n 类型为Rectangle的存储位置,则可以确定其中 n < / i>类型Rectangle的不同实例。如果其中一个类型MyArray的数组Rectangle长度至少为两个,则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)更具可读性。