Some time ago, I mentioned that the PropertyÂValue. method doesn’t create a PropertyÂValue but instead just returns its parameter unchanged. I concluded with the remark, “PropertyÂValue. even exist? I’m not sure. Perhaps it was added for completeness.”
Since that article was written, I did some archaeology, and I think I found the answer.
Originally, the PropertyÂValue did wrap inspectables, which is the Windows Runtime name for what most languages call an “object”. The idea was that the PropertyÂSet would be an IMap<String, PropertyÂValue>, meaning that it was an associative array mapping strings to PropertyÂValue objects. These PropertyÂValue objects could hold value types like integers, or they could hold reference types in the form of an IInspectable, or they could hold arrays of value or reference types, or they could hold nothing at all (“empty”).
In that original design, the PropertyÂValue. method did return a PropertyÂValue whose Type was PropertyÂType.. Similarly, the PropertyÂValue. method returned a PropertyÂValue whose Type was PropertyÂType..
| Value | PropertyType pv.Type |
Creation | Retrieval |
|---|---|---|---|
| Nothing | Empty | CreateEmpty() | N/A |
| Scalar type (e.g. Int32) | Int32 | CreateInt32(v) | pv.GetInt32() |
| Reference type | Inspectable | CreateInspectable(v) | pv.GetInspectable() |
| Array type (e.g. Int32[]) | Int32Array | CreateInt32Array(v) | pv.GetInt32Array() |
You had to wrap inspectables and empties because the PropertyÂSet was a mapping from strings to PropertyÂValue. Everything had to be expressed as a PropertyÂValue.¹ The PropertyÂValue was the fundamental variant type of the Windows Runtime.
At some point, the team decided to change the design and let PropertyÂSet to be a mapping from string to inspectable (object). If the associated value is an object, then you get the corresponding object. If the associated value is empty, then you get null. And if the associated value is a value type, then the value type is wrapped inside a PropertyÂValue, and the PropertyÂValue wrapper acts as the object.
I don’t know why the team changed their mind, but I suspect one of the points in favor of the new design is that the new design matches how most languages already work: C#, JavaScript, Python, Ruby, Scheme, Java, Objective-C all follow the principle that any type can be expressed as an object. (The act of converting value types to objects is known as “boxing”.)
In other words, the design change promoted IInspectable to be the fundamental variant type, and the PropertyÂValue was demoted to being the fundamental boxed type.
A very large change was made to the Windows code base to update the design and to update all the code that had been using the old design. To make the conversion easier, the general shape of the new design matched the old design where it made sense. And this meant that PropertyÂValue. and PropertyÂValue. still existed as functions, but they didn’t do anything interesting. They remained for backward compatibility. Also remaining for backward compatibility are the enumeration values Empty and Inspectable. They remain defined, but nobody uses them.
That takes us to what we have today:
| Value | PropertyType pv.Type |
Creation | Retrieval |
|---|---|---|---|
| Nothing | N/A | null | null |
| Scalar type (e.g. Int32) | Int32 | CreateInt32(v) | pv.GetInt32() |
| Reference type | N/A | v | pv |
| Array type (e.g. Int32[]) | Int32Array | CreateInt32Array(v) | pv.GetInt32Array() |
For backward compatibility, CreateÂEmpty returns null and CreateÂInspectable returns its (non-null) parameter unchanged.
In addition to aligning closer to a large number of popular languages, the new design simplifies the code required to store and retrieve a possibly-null reference in a PropertyÂSet.
// C#
// Old design: CreateInspectable requires a non-null reference
ps.Insert("key", v == null ? PropertyValue.CreateEmpty() : PropertyValue.CreateInspectable(v));
pv = ps.Lookup("key");
if (pv.Type == PropertyValue.Empty) {
v = null;
} else {
v = pv.GetInspectable();
}
// New design: If it's null, then store null
ps.Insert("key", v);
v = ps.Lookup("key");
If you want a function that returns the PropertyÂType for an arbitrary inspectable, you can do this:
// C#
PropertyType WhatIsThisThing(object thing)
{
if (thing is IPropertyValue pv) {
return pv.Type;
} else if (thing is null) {
return PropertyType.Empty;
} else {
return PropertyType.Inspectable;
}
}
// C# 9.0
PropertyType WhatIsThisThing(object thing)
{
return thing switch {
null => PropertyType.Empty,
IPropertyValue pv => pv.Type,
_ => PropertyType.Inspectable,
};
}
// C++/WinRT
PropertyType WhatIsThisThing(IInspectable const& thing)
{
if (thing == nullptr) {
return PropertyType::Empty;
} else if (auto pv = thing.try_as<IPropertyValue>()) {
return pv.Type();
} else {
return PropertyType::Inspectable;
}
}
¹ Since PropertyÂValue is a reference type, they could have decided to use a null pointer to represent the empty state. I assume they explicitly wrapped the empty state for uniformity, rather than forcing people to check for null before querying the Type. Compare the JsonValue which has an explicit object for representing the JSON null rather than using a null pointer.
0 comments
Be the first to start the discussion.