Some time ago, I mentioned that the PropertyValue. method doesn’t create a PropertyValue but instead just returns its parameter unchanged. I concluded with the remark, “PropertyValue. 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 PropertyValue did wrap inspectables, which is the Windows Runtime name for what most languages call an “object”. The idea was that the PropertySet would be an IMap<String, PropertyValue>, meaning that it was an associative array mapping strings to PropertyValue objects. These PropertyValue 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 PropertyValue. method did return a PropertyValue whose Type was PropertyType.. Similarly, the PropertyValue. method returned a PropertyValue whose Type was PropertyType..
| 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 PropertySet was a mapping from strings to PropertyValue. Everything had to be expressed as a PropertyValue.¹ The PropertyValue was the fundamental variant type of the Windows Runtime.
At some point, the team decided to change the design and let PropertySet 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 PropertyValue, and the PropertyValue 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 PropertyValue 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 PropertyValue. and PropertyValue. 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, CreateEmpty returns null and CreateInspectable 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 PropertySet.
// 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 PropertyType 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 PropertyValue 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