拳击和拆箱与泛型

Ita*_*aro 63 .net c# generics boxing unboxing

.NET 1.0创建整数集合的方法(例如)是:

ArrayList list = new ArrayList();
list.Add(i);          /* boxing   */
int j = (int)list[0]; /* unboxing */
Run Code Online (Sandbox Code Playgroud)

使用它的代价是由于装箱和拆箱而缺乏类型安全性和性能.

.NET 2.0方式是使用泛型:

List<int> list = new List<int>();
list.Add(i);
int j = list[0];
Run Code Online (Sandbox Code Playgroud)

拳击的价格(据我所知)是需要在堆上创建一个对象,将堆栈分配的整数复制到新对象,反之亦然,以便取消装箱.

泛型的使用如何克服这个问题?堆栈分配的整数是否保留在堆栈上并从堆中指向(我想这不是这种情况,因为当它超出范围时会发生什么)?似乎仍然需要将其复制到堆栈外的其他地方.

真的发生了什么?

Dan*_*Tao 64

在集合方面,泛型可以通过T[]内部利用实际数组来避免装箱/拆箱.List<T>例如,使用T[]数组来存储其内容.

阵列,当然,是引用类型,因此(在CLR中,内容十分重要的当前版本)存储在堆上.但是因为它是a T[]而不是a object[],所以数组的元素可以"直接"存储:也就是说,它们仍然在堆上,但是它们在数组的堆上而不是被装箱并且数组包含对那些盒子.

因此,对于a List<int>,例如,您在数组中拥有的内容将"看起来"如下:

[ 1 2 3 ]

将此与a进行比较ArrayList,使用an object[]并因此"看"这样的东西:

[ *a *b *c ]

... where *a等等是对象的引用(盒装整数):

*a -> 1
*b -> 2
*c -> 3

请原谅那些粗略的插图; 希望你知道我的意思.


Eri*_*ert 64

您的困惑是由于误解了堆栈,堆和变量之间的关系.这是考虑它的正确方法.

  • 变量是具有类型的存储位置.
  • 变量的生命周期可以是短的也可以是长的."短"是指"直到当前函数返回或抛出"而"长"是指"可能比那个长".
  • 如果变量的类型是引用类型,则变量的内容是对长期存储位置的引用.如果变量的类型是值类型,则变量的内容是值.

作为实现细节,可以在堆栈上分配保证短期存储的存储位置.在堆上分配可能长寿的存储位置.请注意,这并未说明"值类型总是在堆栈上分配".值类型并不总是在堆栈上分配:

int[] x = new int[10];
x[1] = 123;
Run Code Online (Sandbox Code Playgroud)

x[1]是一个存储位置.它是长寿的; 它可能比这种方法寿命更长.因此它必须在堆上.它包含int的事实是无关紧要的.

你正确地说为什么盒装int是昂贵的:

装箱的价格是需要在堆上创建一个对象,将堆栈分配的整数复制到新对象,反之亦然,以便取消装箱.

你出错的地方是说"堆栈分配整数".分配整数的位置无关紧要.重要的是它的存储包含整数,而不是包含对堆位置的引用.价格是需要创建对象并进行复制; 这是唯一相关的成本.

那么为什么通用变量代价不高呢?如果你有一个T类型的变量,并且T被构造为int,那么你有一个int,period类型的变量.int类型的变量是存储位置,它包含int.该存储位置是在堆栈上还是堆上是完全不相关的.相关的是存储位置包含一个int,而不是包含对堆上某些东西的引用.由于存储位置包含int,因此您不必承担装箱和拆箱的成本:在堆上分配新存储并将int复制到新存储.

那现在清楚了吗?

  • 感谢您强调值类型变量保存值,而引用类型变量保存对*其他地方*的信息的引用。值类型和引用类型之间的区别不在于它们是存储在堆栈中还是堆中,而是它们是否存储在变量中。引用类型的自动变量通常将存储在堆栈中,但是由它表示的信息将存储在堆中。 (2认同)