考虑:
int a = 42;
// Reference equality on two boxed ints with the same value
Console.WriteLine( (object)a == (object)a ); // False
// Same thing - listed only for clarity
Console.WriteLine(ReferenceEquals(a, a)); // False
显然,每条装箱指令都分配一个装箱的 Int32
的单独实例,这就是它们之间的引用相等性失败的原因。 This page 似乎表明这是指定的行为:
The box instruction converts the 'raw' (unboxed) value type into an object reference (type O). This is accomplished by creating a new object and copying the data from the value type into the newly allocated object.
但为什么一定要这样呢?
为什么 CLR 不选择保留盒装 Int32
的“缓存”,或者更强大的所有原始值类型(都是不可变的)的通用值,有什么令人信服的理由吗?我知道 Java 有类似的东西。
在没有泛型的时代,对于主要由小整数组成的大型 ArrayList
,它不会在减少内存需求和 GC 工作量方面有很大帮助吗?我还确信存在一些现代 .NET 应用程序确实使用了泛型,但无论出于何种原因(反射、接口(interface)分配等),运行起来都很大-可以通过(看起来)一个简单的优化来大量减少分配。
那是什么原因呢?一些我没有考虑过的性能影响(我怀疑测试该项目是否在缓存中等会导致净性能损失,但我知道什么)?实现困难?不安全代码的问题?破坏向后兼容性(我想不出有什么好的理由说明一个写得很好的程序应该依赖于现有的行为)?或者是其他东西?
编辑:我真正建议的是“经常发生的”基元的静态缓存,much like what Java does。有关示例实现,请参阅 Jon Skeet 的回答。我知道在运行时对任意的、可能可变的值类型或动态“内存”实例执行此操作是完全不同的事情。
编辑:为清楚起见更改了标题。
最佳答案
我 觉得引人注目的一个原因是一致性。正如您所说,Java 确实在一定范围内缓存装箱值...这意味着编写可以一段时间工作的代码太容易了:
// Passes in all my tests. Shame it fails if they're > 127...
if (value1 == value2) {
// Do something
}
我一直被这个困扰 - 幸运的是,诚然是在测试而不是生产代码中,但在给定范围之外显着改变行为的东西仍然令人讨厌。
不要忘记任何条件行为也会对所有装箱操作产生成本——所以在它不使用缓存的情况下,你实际上会发现它更慢(因为它首先必须检查是否使用缓存。
如果你真的想自己写缓存框操作,当然可以这样做:
public static class Int32Extensions
{
private static readonly object[] BoxedIntegers = CreateCache();
private static object[] CreateCache()
{
object[] ret = new object[256];
for (int i = -128; i < 128; i++)
{
ret[i + 128] = i;
}
}
public object Box(this int i)
{
return (i >= -128 && i < 128) ? BoxedIntegers[i + 128] : (object) i;
}
}
然后像这样使用它:
object y = 100.Box();
object z = 100.Box();
if (y == z)
{
// Cache is working
}
关于java - 与 Java 不同,为什么装箱是 .NET 未缓存的原始值类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4256796/