{"id":111673,"date":"2025-10-10T07:00:00","date_gmt":"2025-10-10T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111673"},"modified":"2025-10-10T10:01:41","modified_gmt":"2025-10-10T17:01:41","slug":"20251010-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20251010-00\/?p=111673","title":{"rendered":"The self-assignment principle for Windows Runtime properties: Don&#8217;t change behavior based on whether a property has been written to"},"content":{"rendered":"<p>For the past few days, I&#8217;ve been investigating the self-assignment principle for Windows Runtime properties:<\/p>\n<ul>\n<li>Setting a property to its current value is legal and has no effect.<\/li>\n<\/ul>\n<p>One corollary to this was that <a title=\"Windows Runtime API design principles around read-write properties: Idempotence and self-assignment\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20251008-00\/?p=111660\"> setting a property twice to the same value has no effect<\/a>. Another is that <a title=\"The self-assignment principle for Windows Runtime properties applies to default values\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20251009-00\/?p=111664\"> the default value of a property must be a legal value<\/a>.<\/p>\n<p>Another corollary of this principle is this:<\/p>\n<ul>\n<li>The fact that a property has been set does not by itself trigger a change in behavior.<\/li>\n<\/ul>\n<p>Consider the following API design proposal:<\/p>\n<blockquote class=\"q\">\n<p>The <code>Widget\u00adUpdate\u00adOptions<\/code> class is a parameter passed to the <code>Widget.Update<\/code> method, and it describes how you want to update a widget. The <code>Widget\u00adUpdate\u00adOptions<\/code> class has two properties:<\/p>\n<pre>runtimeclass WidgetUpdateOptions\r\n{\r\n    Windows.UI.Color BackgroundColor;\r\n    Windows.Foundation.Point Position;\r\n}\r\n<\/pre>\n<ul>\n<li><code>BackgroundColor<\/code>: If you set this property, then the widget&#8217;s background color is updated to the color specified.<\/li>\n<li><code>Position<\/code>: If you set this property, then the widget&#8217;s position is updated to the position specified.<\/li>\n<\/ul>\n<\/blockquote>\n<p>This design violates the self-assignment principle.<\/p>\n<p>Suppose we have this code fragment:<\/p>\n<pre>var options = new WidgetUpdateOptions();\r\noptions.BackgroundColor = Color.Blue;\r\nwidget.Update(options);\r\n<\/pre>\n<p>The above fragment updates the widget background color and leaves the position unchanged.<\/p>\n<p>We then introduce a harmless self-assignment:<\/p>\n<pre>var options = new WidgetUpdateOptions();\r\noptions.BackgroundColor = Color.Blue;\r\n<span style=\"border: solid 1px currentcolor;\">options.Position = options.Position;<\/span>\r\nwidget.Update(options);\r\n<\/pre>\n<p>This time, since there was an assignment to <code>options.Position<\/code>, the call to <code>widget.Update(options)<\/code> updates not just the background color, but it also updates the position, probably to (0,0). Oops.<\/p>\n<p>One way to fix this is to have a special &#8220;no change&#8221; sentinel value. For example, you might say that a color of Transparent means &#8220;do not change the color&#8221; and a position of (NaN, NaN) means &#8220;do not change the position&#8221;. Those special values would also be the default values for the properties.<\/p>\n<p>One downside of sentinel values is that there might not be a good choice of sentinel value. For example, what if you want a Widget to have a transparent background? Another is that there may not be an <i>obvious<\/i> choice for sentinel value. We sort of had to invent a weird sentinel value of (NaN, NaN) for the &#8220;no point&#8221; point. And it might be hard to generate a NaN value in some languages.<\/p>\n<p>The Windows Runtime supports nullable types, so you can make the properties nullable, where the null value means &#8220;leave unchanged.&#8221; This is more natural and discoverable.<\/p>\n<pre>runtimeclass WidgetUpdateOptions\r\n{\r\n    Windows.Foundation.IReference&lt;Windows.UI.Color&gt; BackgroundColor;\r\n    Windows.Foundation.IReference&lt;Windows.Foundation.Point&gt; Position;\r\n}\r\n<\/pre>\n<ul>\n<li><code>BackgroundColor<\/code>: If you set this property to a non-null value, then the widget&#8217;s background color is updated to the color specified.<\/li>\n<li><code>Position<\/code>: If you set this property to a non-null value, then the widget&#8217;s position is updated to the position specified.<\/li>\n<\/ul>\n<p>Another solution is to change the properties to methods. Methods are allowed to have side effects.<\/p>\n<pre>runtimeclass WidgetUpdateOptions\r\n{\r\n    void UpdateBackgroundColor(Windows.UI.Color backgroundColor);\r\n    void UpdatePosition(Windows.Foundation.Point position);\r\n}\r\n<\/pre>\n<p><b>Related reading<\/b>: <a title=\"API design principle: Reading a property or adding an event handler should not alter observable behavior\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20231016-00\/?p=108895\"> API design principle: Reading a property or adding an event handler should not alter observable behavior<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The fact that a property has been set does not by itself trigger functionality.<\/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],"class_list":["post-111673","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>The fact that a property has been set does not by itself trigger functionality.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111673","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=111673"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111673\/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=111673"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111673"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111673"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}