Boxing and unboxing with generics
.NET 1.0创建整数集合的方法(例如)是:
1 2 3 |
使用此项的惩罚是由于装箱和拆箱而缺乏类型安全性和性能。
.NET 2.0的方法是使用泛型:
1 2 3 |
装箱的代价(据我所知)是需要在堆上创建一个对象,将分配给新对象的堆栈复制到新对象,反之亦然。
如何使用仿制药来克服这一点?堆栈分配的整数是否保留在堆栈上并从堆中被指向(我想这不是这样,因为当堆栈超出范围时会发生什么情况)?似乎仍然需要将它复制到堆栈之外的其他地方。
到底发生了什么?
当涉及到集合时,通过在内部使用实际的
当然,数组是引用类型,因此(在当前的clr版本中,yada yada)存储在堆中。但是,由于它是一个
例如,对于一个
1 | [ 1 2 3 ] |
将其与使用
1 | [ *a *b *c ] |
…其中
1 2 3 | *a -> 1 *b -> 2 *c -> 3 |
请原谅那些粗俗的插图,希望你知道我的意思。
您的困惑是由于误解了堆栈、堆和变量之间的关系。这是正确的思考方法。
- 变量是具有类型的存储位置。
- 变量的生存期可以是短的,也可以是长的。"short"的意思是"直到当前函数返回或抛出为止","long"的意思是"可能比这长"。
- 如果变量的类型是引用类型,则变量的内容是对长期存储位置的引用。如果变量的类型是值类型,则变量的内容是值。
作为一个实现细节,可以在堆栈上分配一个保证寿命短的存储位置。堆上分配了可能长期存在的存储位置。请注意,这与"值类型总是在堆栈上分配"无关。值类型并不总是在堆栈上分配:
1 2 |
你说得对,为什么盒装int很贵:
The price of boxing is the need to create an object on the heap, copy the stack allocated integer to the new object and vice-versa for unboxing.
出错的地方是说"堆栈分配的整数"。整数的分配位置无关紧要。重要的是它的存储包含整数,而不是包含对堆位置的引用。价格是创建对象和复制的需要;这是唯一相关的成本。
那么,为什么通用变量不昂贵呢?如果您有一个类型为t的变量,并且t被构造为int,那么您有一个类型为int,period的变量。int类型的变量是一个存储位置,它包含一个int。该存储位置是在堆栈上还是堆上完全无关。相关的是存储位置包含一个int,而不是包含对堆上某个对象的引用。由于存储位置包含一个int,因此不必承担装箱和取消装箱的成本:在堆上分配新存储,并将int复制到新存储。
现在明白了吗?
泛型允许列表的内部数组类型为
以下是没有仿制药的情况:
这里有三个间接层次:
泛型:
因此只有两个间接的层次:
其他一些差异:
- 非泛型方法将需要8或12个字节(一个指针,一个int)的和来存储值,一个分配中存储4/8,另一个分配中存储4。这可能更多的是由于对齐和填充。泛型方法在数组中只需要4个字节的空间。
- 非泛型方法需要分配装箱的int;泛型方法不需要。这速度更快,减少了GC的搅动。
- 非泛型方法需要强制转换来提取值。这不是类型安全的,而且速度慢了一点。
arraylist只处理类型
使用通用列表时,编译器会为该值类型输出专用代码,以便实际值存储在列表中,而不是对包含值的对象的引用。因此不需要拳击。
The price of boxing (to my understanding) is the need to create an object on the heap, copy the stack allocated integer to the new object and vice-versa for unboxing.
我认为您假设值类型总是在堆栈上实例化。情况并非如此——它们可以在堆、堆栈或寄存器中创建。有关这方面的更多信息,请参阅EricLippert的文章:关于值类型的真相。
为什么你会考虑用
其中泛型起作用的是
在.NET 1中,当调用
在.NET 2中:
是的,仍然复制