6
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不需要创建类型的副本,而是将指向该值的指针压入堆栈。只有分配会创建一个副本。

4

3 回答 3

11

由于值类型的工作方式,装箱Point的是原始的副本,并且通过强制转换为“拆箱”它Point会创建另一个副本。来自 C# 语言规范(第 1.3 节,“类型和变量”):

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

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

至于C++……嗯……当然,C#的规则不一定适用。:) CLR 在指针和引用方面实际上比您最初想象的要灵活得多,而 C++(以这种灵活性着称)可能会利用它。

于 2013-06-24T16:42:26.797 回答
1

您不能这样做,因为取消装箱的结果是装箱值的副本,而不是装箱值本身。并且强制转换object为值类型是拆箱的定义。因此,如果编译器允许您这样做,那将是非常混乱的,因为分配实际上不会做任何事情。

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

于 2013-06-24T17:00:39.133 回答
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);
    }

正如文档所指出的,我认为它被认为是不安全的原因是您不能使用不可变的内置类型 [eg Unbox<int>(i) = 42] 来执行此操作,并且系统不会执行此操作。

于 2019-11-01T00:24:49.623 回答