{"id":108895,"date":"2023-10-16T07:00:00","date_gmt":"2023-10-16T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108895"},"modified":"2023-10-16T07:01:08","modified_gmt":"2023-10-16T14:01:08","slug":"20231016-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20231016-00\/?p=108895","title":{"rendered":"API design principle: Reading a property or adding an event handler should not alter observable behavior"},"content":{"rendered":"<p>On of the Windows Runtime API design principles is that reading a property\u00b9 or adding an empty event handler should not affect the API&#8217;s proper usage. It is legal for the implementation to optimize based on whether a property was accessed or whether a handler is registered,\u00b2 but the optimization should not affect overall correctness.<\/p>\n<p>Here are examples of bad behavior we want to avoid:<\/p>\n<blockquote class=\"q\"><p>If you read the <code>Widget.Stream<\/code> property, you must call the <code>Close<\/code> method on the returned stream.<\/p><\/blockquote>\n<blockquote class=\"q\"><p>If you add a handler for the <code>FancyReady<\/code> event, then the <code>PlainReady<\/code> event is not raised.<\/p><\/blockquote>\n<blockquote class=\"q\"><p>The <code>MischiefDetected<\/code> event handler must call <code>MischiefManaged<\/code> before returning.<\/p><\/blockquote>\n<p>The reason for the &#8220;reading a property should not affect proper usage&#8221; guideline is that many debuggers will &#8220;helpfully&#8221; dump the properties of an object. In the case of the <code>Stream<\/code> property above, if reading the <code>Stream<\/code> creates an obligation to <code>Close<\/code> it, then each time you hover over a widget or log it to the console, the debugger will read the <code>Stream<\/code> property and show it on the screen. The debugger doesn&#8217;t know the special rule about having to <code>Close<\/code> the stream, so the stream will go unclosed, and you have a memory leak.<\/p>\n<p>Even worse, that stream may be associated with an open file handle, so now you leaked a file handle, and the effects of a leaked file handle can be quite severe. Debugging is hard enough. Don&#8217;t create a situation where <i>a bug is introduced by the presence of a debugger<\/i>. &#8220;Yeah, we can&#8217;t run this program under the debugger to figure out what is going wrong, because once we run it under the debugger, it crashes with a sharing violation.&#8221;<\/p>\n<p>It is also common, especially when learning how to use a new feature, to add handlers to every event, where all the handler does is log a message like &#8220;FancyReady received&#8221; followed by the values of all of the event arguments. This lets developers see the event flow and gain a better mental model of how the feature works. But if adding an event handler changes the feature&#8217;s proper behavior, you create a version of the Heisenberg Uncertainty Principle: Attempting to observe the system changes its behavior.<\/p>\n<p>And you definitely don&#8217;t want to put people into a position where they throw up their hands in frustration and say, &#8220;I don&#8217;t understand. Once we connect a debugger or turn on logging, the program crashes even before we get to the problem we&#8217;re trying to solve. This problem is un-debuggable.&#8221;<\/p>\n<p>\u00b9 You are allowed to raise an exception from a property access if the situation calls for it.<\/p>\n<p>\u00b2 You are allowed to require that a handler be registered for an event. That doesn&#8217;t violate the principle, because you&#8217;re saying that omitting the handler is was never proper API usage to begin with. (In C\/C++ terms, it is &#8220;undefined behavior&#8221;.) It does mean that if the developer adds a dummy handler that just logs information but does no work, they might inadvertently &#8220;fix&#8221; their program. In the case of improper usage, you should pass a custom message to <code>Ro\u00adOriginate\u00adError<\/code> to remind the developer why the operation failed. &#8220;You must register a MuffinReady handler before you can Bake().&#8221;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Don&#8217;t punish people for looking.<\/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-108895","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Don&#8217;t punish people for looking.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108895","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=108895"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108895\/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=108895"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108895"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108895"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}