{"id":111725,"date":"2025-10-24T07:00:00","date_gmt":"2025-10-24T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111725"},"modified":"2025-10-24T08:53:38","modified_gmt":"2025-10-24T15:53:38","slug":"20251024-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20251024-00\/?p=111725","title":{"rendered":"The early history of the Windows Runtime PropertyValue and why there is a PropertyType.Inspectable that is never used"},"content":{"rendered":"<p>Some time ago, I mentioned that <a title=\"If the Window Runtime PropertyValue is for boxing non-inspectables, why is there a PropertyValue.CreateInspectable?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250717-00\/?p=111388\"> the <code>Property\u00adValue.<wbr \/>Create\u00adInspectable<\/code> method doesn&#8217;t create a <code>Property\u00adValue<\/code><\/a> but instead just returns its parameter unchanged. I concluded with the remark, &#8220;<code>Property\u00adValue.<wbr \/>Create\u00adInspectable<\/code> even exist? I&#8217;m not sure. Perhaps it was added for completeness.&#8221;<\/p>\n<p>Since that article was written, I did some archaeology, and I think I found the answer.<\/p>\n<p>Originally, the <code>Property\u00adValue<\/code> did wrap inspectables, which is the Windows Runtime name for what most languages call an &#8220;object&#8221;. The idea was that the <code>Property\u00adSet<\/code> would be an <code>IMap&lt;String, Property\u00adValue&gt;<\/code>, meaning that it was an associative array mapping strings to <code>Property\u00adValue<\/code> objects. These <code>Property\u00adValue<\/code> objects could hold value types like integers, or they could hold reference types in the form of an <code>IInspectable<\/code>, or they could hold arrays of value or reference types, or they could hold nothing at all (&#8220;empty&#8221;).<\/p>\n<p>In that original design, the <code>Property\u00adValue.<wbr \/>Create\u00adInspectable<\/code> method did return a <code>Property\u00adValue<\/code> whose <code>Type<\/code> was <code>Property\u00adType.<wbr \/>Inspectable<\/code>. Similarly, the <code>Property\u00adValue.<wbr \/>Create\u00adEmpty<\/code> method returned a <code>Property\u00adValue<\/code> whose <code>Type<\/code> was <code>Property\u00adType.<wbr \/>Empty<\/code>.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Value<\/th>\n<th>PropertyType<br \/>\n<tt>pv.Type<\/tt><\/th>\n<th>Creation<\/th>\n<th>Retrieval<\/th>\n<\/tr>\n<tr>\n<td>Nothing<\/td>\n<td><tt>Empty<\/tt><\/td>\n<td><tt>CreateEmpty()<\/tt><\/td>\n<td>N\/A<\/td>\n<\/tr>\n<tr>\n<td>Scalar type (e.g. <tt>Int32<\/tt>)<\/td>\n<td><tt>Int32<\/tt><\/td>\n<td><tt>CreateInt32(v)<\/tt><\/td>\n<td><tt>pv.GetInt32()<\/tt><\/td>\n<\/tr>\n<tr>\n<td>Reference type<\/td>\n<td><tt>Inspectable<\/tt><\/td>\n<td><tt>CreateInspectable(v)<\/tt><\/td>\n<td><tt>pv.GetInspectable()<\/tt><\/td>\n<\/tr>\n<tr>\n<td>Array type (e.g. <tt>Int32[]<\/tt>)<\/td>\n<td><tt>Int32Array<\/tt><\/td>\n<td><tt>CreateInt32Array(v)<\/tt><\/td>\n<td><tt>pv.GetInt32Array()<\/tt><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>You had to wrap inspectables and empties because the <code>Property\u00adSet<\/code> was a mapping from strings to <code>Property\u00adValue<\/code>. Everything had to be expressed as a <code>Property\u00adValue<\/code>.\u00b9 The <code>Property\u00adValue<\/code> was the fundamental variant type of the Windows Runtime.<\/p>\n<p>At some point, the team decided to change the design and let <code>Property\u00adSet<\/code> 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 <code>Property\u00adValue<\/code>, and the <code>Property\u00adValue<\/code> wrapper acts as the object.<\/p>\n<p>I don&#8217;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 &#8220;boxing&#8221;.)<\/p>\n<p>In other words, the design change promoted <code>IInspectable<\/code> to be the fundamental variant type, and the <code>Property\u00adValue<\/code> was demoted to being the fundamental boxed type.<\/p>\n<p>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 <code>Property\u00adValue.<wbr \/>Create\u00adInspectable<\/code> and <code>Property\u00adValue.<wbr \/>Create\u00adEmpty<\/code> still existed as functions, but they didn&#8217;t do anything interesting. They remained for backward compatibility. Also remaining for backward compatibility are the enumeration values <code>Empty<\/code> and <code>Inspectable<\/code>. They remain defined, but nobody uses them.<\/p>\n<p>That takes us to what we have today:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Value<\/th>\n<th>PropertyType<br \/>\n<tt>pv.Type<\/tt><\/th>\n<th>Creation<\/th>\n<th>Retrieval<\/th>\n<\/tr>\n<tr>\n<td>Nothing<\/td>\n<td>N\/A<\/td>\n<td><tt>null<\/tt><\/td>\n<td><tt>null<\/tt><\/td>\n<\/tr>\n<tr>\n<td>Scalar type (e.g. <tt>Int32<\/tt>)<\/td>\n<td><tt>Int32<\/tt><\/td>\n<td><tt>CreateInt32(v)<\/tt><\/td>\n<td><tt>pv.GetInt32()<\/tt><\/td>\n<\/tr>\n<tr>\n<td>Reference type<\/td>\n<td>N\/A<\/td>\n<td><tt>v<\/tt><\/td>\n<td><tt>pv<\/tt><\/td>\n<\/tr>\n<tr>\n<td>Array type (e.g. <tt>Int32[]<\/tt>)<\/td>\n<td><tt>Int32Array<\/tt><\/td>\n<td><tt>CreateInt32Array(v)<\/tt><\/td>\n<td><tt>pv.GetInt32Array()<\/tt><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For backward compatibility, <tt>Create\u00adEmpty<\/tt> returns <code>null<\/code> and <tt>Create\u00adInspectable<\/tt> returns its (non-null) parameter unchanged.<\/p>\n<p>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 <code>Property\u00adSet<\/code>.<\/p>\n<pre>\/\/ C#\r\n\r\n\/\/ Old design: CreateInspectable requires a non-null reference\r\nps.Insert(\"key\", v == null ? PropertyValue.CreateEmpty() : PropertyValue.CreateInspectable(v));\r\n\r\npv = ps.Lookup(\"key\");\r\nif (pv.Type == PropertyValue.Empty) {\r\n    v = null;\r\n} else {\r\n    v = pv.GetInspectable();\r\n}\r\n\r\n\/\/ New design: If it's null, then store null\r\nps.Insert(\"key\", v);\r\n\r\nv = ps.Lookup(\"key\");\r\n<\/pre>\n<p>If you want a function that returns the <code>Property\u00adType<\/code> for an arbitrary inspectable, you can do this:<\/p>\n<pre>\/\/ C#\r\nPropertyType WhatIsThisThing(object thing)\r\n{\r\n    if (thing is IPropertyValue pv) {\r\n        return pv.Type;\r\n    } else if (thing is null) {\r\n        return PropertyType.Empty;\r\n    } else {\r\n        return PropertyType.Inspectable;\r\n    }\r\n}\r\n\r\n\/\/ C# 9.0\r\nPropertyType WhatIsThisThing(object thing)\r\n{\r\n    return thing switch {\r\n        null =&gt; PropertyType.Empty,\r\n        IPropertyValue pv =&gt; pv.Type,\r\n        _ =&gt; PropertyType.Inspectable,\r\n    };\r\n}\r\n\r\n\/\/ C++\/WinRT\r\nPropertyType WhatIsThisThing(IInspectable const&amp; thing)\r\n{\r\n    if (thing == nullptr) {\r\n        return PropertyType::Empty;\r\n    } else if (auto pv = thing.try_as&lt;IPropertyValue&gt;()) {\r\n        return pv.Type();\r\n    } else {\r\n        return PropertyType::Inspectable;\r\n    }\r\n}\r\n<\/pre>\n<p>\u00b9 Since <code>Property\u00adValue<\/code> 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 <code>Type<\/code>. Compare the <code>JsonValue<\/code> which has an explicit object for representing the JSON <code>null<\/code> rather than using a null pointer.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It used to be there because PropertyValue started out as the fundamental variant type before shifting focus to being the fundamental boxed type.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25,2],"class_list":["post-111725","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code","tag-history"],"acf":[],"blog_post_summary":"<p>It used to be there because PropertyValue started out as the fundamental variant type before shifting focus to being the fundamental boxed type.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111725","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=111725"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111725\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=111725"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111725"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111725"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}