为什么我不能修改取消装箱转换的结果?

时间:2013-06-24 16:37:07

标签: c# il boxing unboxing

struct Point
{
    public int x;
    public int y;
}
void Main()
{
    Point p;
    p.x = 1;
    p.y = 1;
    Object o = p;
    ((Point) o).x = 4; // error
    ((Point) o).x = 5; // error
    ((Point) o).x = 6; // error
    p = (Point) o  // expect 6
}

为什么不编译到

ldloc.1 // o
unbox Point
ldc.i4.4
stfld Point.x

C ++ CLI允许的地方。

对于那些不知道的人,unbox需要not来创建value types的副本,而是将指针推送到堆栈上。 只有作业才能创建副本。

3 个答案:

答案 0 :(得分:8)

由于值类型的工作原理,装箱的Point是原始版本的副本,而通过转换回Point“取消装箱”会创建另一个副本。从C#语言规范(§1.3,“类型和变量”):

  

当值类型的值转换为类型对象时,将分配一个对象实例(也称为“框”)来保存该值,并将该值复制到该框中。相反,当对象引用转换为值类型时,将检查引用的对象是否为正确值类型的框,如果检查成功,则复制框中的值。

修改副本无论如何都不会改变原件,因此允许它没有多大意义。

至于C ++ ......当然,C#的规则并不一定适用于它。 :) CLR实际上对指针和引用的灵活性比你最初想象的要大得多,而C ++ - 以这种灵活性着称 - 可能会利用它。

答案 1 :(得分:1)

您无法执行此操作,因为取消装箱的结果是装箱值的副本,而不是装箱值本身。将object转换为值类型是拆箱的定义。因此,如果编译器允许您这样做,那将非常令人困惑,因为分配实际上不会做任何事情。

认为你的代码在C ++ / CLI中工作的原因是因为该语言通常更多地支持使用引用工作(或不支持),包括强类型框(例如{{1} })并将(某些)类视为值类型(例如,使用Point^而不使用MemoryStream)。

答案 2 :(得分:0)

您可以使用System.Runtime.CompilerService.Unsafe.Unbox函数来完成此操作:

    static void Main()
    {
        Point p;
        p.x = 1;
        p.y = 1;
        Object o = p;
        Unsafe.Unbox<Point>(o).x = 6; // error
        p = (Point)o;  // 6
        Console.WriteLine(p.x);
    }

如文档所述,我认为它被认为是不安全的原因是,您不得使用不可变的内置类型[例如Unbox<int>(i) = 42],系统将不执行任何操作。