Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/338.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
为什么装箱在.NET中是未缓存的原始值类型,与Java不同?_Java_.net_Memory Management_Boxing_Value Type - Fatal编程技术网

为什么装箱在.NET中是未缓存的原始值类型,与Java不同?

为什么装箱在.NET中是未缓存的原始值类型,与Java不同?,java,.net,memory-management,boxing,value-type,Java,.net,Memory Management,Boxing,Value Type,考虑: 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的单独实例,

考虑:

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
的单独实例,这就是它们之间的引用相等失败的原因。似乎表明这是指定的行为:

box指令转换“原始” (未装箱)将值类型放入对象中 参考(O型)。这是 通过创建新对象来完成 以及从值复制数据 键入新分配的对象

但为什么一定要这样呢? CLR没有选择为所有原语值类型(都是不可变的)保存盒装
Int32
s或更强大的公共值的“缓存”,这有什么令人信服的原因吗?我知道Java有这样的东西

在没有泛型的日子里,对于主要由小整数组成的大型
ArrayList
,它在减少内存需求和GC工作负载方面不是有很大帮助吗?我还确信有几个现代的.NET应用程序确实使用泛型,但无论出于何种原因(反射、接口分配等),都会运行大量的装箱分配,可以通过(似乎是)简单的优化来大幅减少

那么原因是什么呢?一些我没有考虑的性能影响(我怀疑测试项目是否在缓存中等是否会导致净性能损失,但我知道什么)?实施困难?不安全代码的问题?破坏向后兼容性(我想不出任何好的理由来解释为什么一个编写良好的程序应该依赖于现有的行为)?还是别的什么

编辑:我真正建议的是“常见”原语的静态缓存。有关示例实现,请参见Jon Skeet的答案。我理解,对任意的、可能可变的值类型或在运行时动态“记忆”实例执行此操作是完全不同的事情

编辑:为了清晰起见更改了标题。

我发现一个引人注目的原因是一致性。正如您所说,Java确实在一定范围内缓存装箱的值。。。这意味着编写一段时间内有效的代码太容易了:

幸运的是,我一直被这一点困扰着——诚然,这是在测试中,而不是在生产代码中,但在给定的范围之外,有一些东西显著地改变了行为,这仍然是令人讨厌的

不要忘记,任何条件行为也会导致所有装箱操作的成本——因此,在它不使用缓存的情况下,您实际上会发现它的速度较慢(因为它首先必须检查是否使用缓存)

当然,如果您确实想编写自己的缓存盒操作,您可以这样做:

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;
    }
}

我不能说自己能读懂思想,但这里有几个因素:

1) 缓存值类型可能导致不可预测性—比较两个相等的装箱值可能是真或假,具体取决于缓存命中率和实现。哎哟

2) 装箱值类型的生存期很可能很短,那么在缓存中保存该值的时间有多长?现在,您要么拥有大量不再使用的缓存值,要么需要使GC实现更加复杂,以跟踪缓存值类型的生存期


面对这些不利因素,潜在的胜利是什么?在执行大量等值类型的长寿命装箱的应用程序中,内存占用更小。由于这一胜利将影响少数应用程序,并且可以通过更改代码来解决,因此我同意c#spec编写者的决定。

装箱值对象不一定是不变的。可以更改装箱值类型中的值,例如通过接口

因此,如果装箱一个值类型总是基于相同的原始值返回相同的实例,那么它将创建可能不合适的引用(例如,两个不同的值类型实例碰巧具有相同的值,但最终使用相同的引用,即使它们不应该)

生成此输出:

原始。X=1

原始。Y=2

框1.X=3

框1.Y=4

框2.X=1

框2.Y=2


如果装箱没有创建新实例,那么boxed1和boxed2将具有相同的值,如果它们是从不同的原始值类型实例创建的,这将是不合适的。

对此有一个简单的解释:取消装箱很快。它需要回到.NET 1.x版本。JIT编译器为其生成机器代码后,只为其生成少量CPU指令,所有指令都是内联的,没有方法调用。不包括可空类型和大型结构之类的角点情况


查找缓存值的工作将大大降低此代码的速度。

我认为运行时填充缓存不是一个好主意,但我认为在64位系统上,将大约80亿个可能的64/5百万对象引用值定义为整数或浮点字面值可能是合理的,在任何系统的前置框中,所有基本文本。测试引用类型的高31位是否持有某些值可能比内存引用便宜。

添加到已经列出的答案中的事实是,在.net中,至少在普通垃圾收集器中,对象引用在内部存储为直接指针。这意味着当执行垃圾收集时,系统必须更新对每个移动对象的每个引用,但这也意味着“主线”操作可以非常快。如果对象引用有时是直接指针,有时是其他对象,那么每次取消引用对象时都需要额外的代码。由于对象取消引用是.net程序执行过程中最常见的操作之一,因此即使是5%的减速也会导致devas
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
}
public interface IBoxed
{
    int X { get; set; }
    int Y { get; set; }
}

public struct BoxMe : IBoxed
{
    public int X { get; set; }

    public int Y { get; set; }
}

public static void Test()
{
    BoxMe original = new BoxMe()
                        {
                            X = 1,
                            Y = 2
                        };
    
    object boxed1 = (object) original;
    object boxed2 = (object) original;

    ((IBoxed) boxed1).X = 3;
    ((IBoxed) boxed1).Y = 4;

    Console.WriteLine("original.X = " + original.X);
    Console.WriteLine("original.Y = " + original.Y);
    Console.WriteLine("boxed1.X = " + ((IBoxed)boxed1).X);
    Console.WriteLine("boxed1.Y = " + ((IBoxed)boxed1).Y);
    Console.WriteLine("boxed2.X = " + ((IBoxed)boxed2).X);
    Console.WriteLine("boxed2.Y = " + ((IBoxed)boxed2).Y);
}