问题
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
}
Why doesn't it compile to
ldloc.1 // o
unbox Point
ldc.i4.4
stfld Point.x
Where C++ CLI allows it.
For those who don't know, unbox
is not required to create a copy of value types, instead it pushes a pointer to the value on to the stack.
Only assignment would create a copy.
回答1:
Because of how value types work, the boxed Point
is a copy of the original, and "unboxing" it by casting back to Point
creates yet another copy. From the C# language spec (§1.3, "Types and Variables"):
When a value of a value type is converted to type object, an object instance, also called a “box,” is allocated to hold the value, and the value is copied into that box. Conversely, when an object reference is cast to a value type, a check is made that the referenced object is a box of the correct value type, and, if the check succeeds, the value in the box is copied out.
Modifying the copy wouldn't change the original anyway, so it wouldn't make much sense to allow it.
As for C++...well...of course, the rules of C# don't necessarily apply to it. :) The CLR actually has quite a bit more flexibility with pointers and references than you'd first think, and C++ -- being known for such flexibility -- probably takes advantage of it.
回答2:
You can't do this, because the result of unboxing is a copy of the boxed value, not the boxed value itself. And casting object
to a value type is the definition of unboxing. So, if the compiler allowed you to do this, it would be very confusing, because the assignments wouldn't actually do anything.
I think the reason your code works in C++/CLI is because that language in general has more support for working (or not) with references, including strongly-typed boxes (e.g. Point^
) and treating (some) classes as value types (e.g. using MemoryStream
without ^
).
回答3:
You can accomplish this using the System.Runtime.CompilerService.Unsafe.Unbox function:
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);
}
As the documentation notes, and I presume the reason it is considered unsafe, is that you must not do this with an immutable built-in type [e.g. Unbox<int>(i) = 42
], and the system does nothing to enforce this.
来源:https://stackoverflow.com/questions/17280547/why-can-i-not-modify-the-result-of-an-unboxing-conversion