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的副本,而是将指针推送到堆栈上。
只有作业才能创建副本。
答案 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
],系统将不执行任何操作。