When you box a value in the Windows Runtime, it’s boxed for good. You can’t change the value inside a box. Most of the time, it’s pretty obvious that you can’t do it:
// C# void UpdateX(Nullable<Point> pt) { if (pt != null) { // Error: Cannot modify the return value of Point?.Value // because it is not a variable pt.Value.X = 42.0; } } // C++/WinRT void UpdateX(IReference<Point> pt) { if (pt != nullptr) { // Error: expression must be a modifiable lvalue pt.Value().X = 42.0; } } // C++/CX void UpdateX(IBox<Point>^ pt) { if (pt != nullptr) { // Error: lvalue required as left operand of assignment pt->Value.X = 42.0; } }
In the case of C++/WinRT, I think the error is unlikely to occur because you’re clearly modifying the result of a function call.
But for C# and C++/CX, the property syntax looks a lot like a member variable access, and you may not realize that the property value is secretly the result of a function call.
Boxed values are read-only. If you look at IReference<T>
(which is the interface at the ABI layer that is used for Windows Runtime boxed values), you’ll see that there is a read-only Value
property, but no method for setting a new value. Once a value is boxed, you can’t change it.
But what if you want to change it?
You’ll have to box up a new value and then ask everybody to switch over to it.
// C# Nullable<Point> UpdateX(Nullable<Point> pt) { if (pt != null) { var value = pt.Value; value.X = 42.0; pt = value; // box up a new value } return pt; } // C++/WinRT IReference<Point> UpdateX(IReference<Point> pt) { if (pt != nullptr) { auto value = pt.Value(); value.X = 42.0; pt = winrt::box_value(value); // box up a new value } return pt; } // C++/CX IBox<Point> UpdateX(IBox<Point>^ pt) { if (pt != nullptr) { auto value = pt->Value; value.X = 42.0; pt = value; // box up a new value } return pt; }
Of course, you now have to take the updated boxed value and update wherever you got it from.
// C# flyoutShowOptions.Position = UpdateX(flyoutShowOptions.Position); // C++/WinRT flyoutShowOptions.Position(UpdateX(flyoutShowOptions.Position())); // C++/CX flyoutShowOptions->Position = UpdateX(flyoutShowOptions->Position);
"Boxing" means the conversion of a value type to a reference type. In your C# example, there is no boxing or unboxing; the compiler error occurs not because pt.Value is "boxed" but rather because a struct returned by a property is classified as a read-only value instead of a variable. Here's a better example:
<code>
You are correct, and there is more. Passing the boxed value as a function parameter defeats the purpose of showing anything about writing to a boxed value. Even if
pt
was an integer, writing to it would have served no purpose.This whole blog post looks like a justification looking for a problem. (So far, I had only seen people writing solutions in search of a problem!)
This blog post is an answer to a real question from a customer. So there was at least one person who had this problem of wanting to modify a boxed value.
Did the customer also receive the boxed value through a method’s parameter? Or did their situation have merit?
I’m not asking questions; they are rhetorical. What I mean to say is, cutting down the noise brings out the point you want to make. That’s all.
The customer wanted to create a boxed
Point
with a specific value, and their plan was to create a boxed point and then modifying the boxed value:IBox^ pt = Point(); pt->Value.X = 42.0f; pt->Value.Y = 32.0f;
Well, if you are writing a Windows Runtime component then yes it is very possible that this could come in through the parameters. Since the component would also have to project back to the Windows Runtime type system, then this situation in general is valid.
You can also see this entire situation in the UWP, one example that I know of is Windows.UI.WindowManagement.AppWindowTitleBar.BackgroundColor. For C++/WinRT this has the signature of:
<code>
For C# this has...
The box is a reference type, and in C# reference type objects are usually used as containers of mutable values whose mutation is observed by every party holding a reference to the object. Raymond points out that contrary to that pattern, the contents of the box is immutable, and therefore it cannot be used for shared mutation.
And I am pointing out that Mr. Chen's demonstration has too much noise that obscures the main point. The function definition, the parameter, the Nullable class, and even the mention of Windows Runtime are extra.
C# features cooperative autoboxing of primitive types via the Object class. But if you type out the code snippet of Michael Liu, the .NET compiler will say, "Cannot modify the result of an unboxing conversion." Thankfully, that message makes it clear...
Raymond is probably talking about “boxing” in the Windows Runtime sense, not the .NET sense.