{"id":107565,"date":"2022-12-08T07:00:00","date_gmt":"2022-12-08T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107565"},"modified":"2022-12-08T07:36:46","modified_gmt":"2022-12-08T15:36:46","slug":"20221208-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20221208-00\/?p=107565","title":{"rendered":"If you&#8217;re going to wrap a Windows Runtime event, you may as well let the wrapped event source manage the token"},"content":{"rendered":"<p>Suppose that you have a Windows Runtime object with an event that is just a proxy for an event on some other object. For example, maybe we have this:<\/p>\n<pre>runtimeclass Widget\r\n{\r\n    event Windows.Foundation.TypedEventHandler&lt;Widget, Object&gt; Changed;\r\n\r\n    \/* ... other members ... *\/\r\n}\r\n\r\nruntimeclass Dashboard\r\n{\r\n    event Windows.Foundation.TypedEventHandler&lt;Dashboard, Object&gt; Changed;\r\n\r\n    \/* ... other members ... *\/\r\n}\r\n<\/pre>\n<p>Secretly, a <code>Dashboard<\/code> uses a <code>Widget<\/code> to do the real work, and the <code>Changed<\/code> event on the <code>Dashboard<\/code> is just a wrapper around the <code>Widget<\/code>&#8216;s <code>Changed<\/code> event. The <code>Dashboard<\/code> registers for the <code>Widget<\/code>&#8216;s <code>Changed<\/code> event, and the event handler turns around and raises the <code>Dashboard<\/code>&#8216;s <code>Changed<\/code> event.<\/p>\n<pre>namespace winrt::Namespace::implementation\r\n{\r\n    struct Dashboard : DashboardT&lt;Dashboard&gt;\r\n    {\r\n        Namespace::Widget const m_widget;\r\n        event&lt;TypedEventHandler&lt;Namespace::Dashboard, IInspectable&gt; m_changed;\r\n\r\n        Dashboard()\r\n        {\r\n            m_widget.Changed({ get_weak(), &amp;Dashboard::OnChanged });\r\n        }\r\n\r\n        auto Changed(TypedEventHandler&lt;Namespace::Dashboard, IInspectable&gt; const&amp; handler)\r\n        {\r\n            return m_changed.add(handler);\r\n        }\r\n\r\n        void Changed(event_token token)\r\n        {\r\n            return m_changed.remove(token);\r\n        }\r\n\r\n        void OnChanged(Namespace::Widget const&amp;, IInspectable const&amp;)\r\n        {\r\n            m_event(*this, nullptr);\r\n        }\r\n    };\r\n}\r\n<\/pre>\n<p>The idea here is that the <code>Dashboard<\/code> listens on the <code>Changed<\/code> event of its private <code>Widget<\/code>, and when the <code>Widget<\/code> changes, it turns around and raises its own <code>Changed<\/code> event to anybody who is listening.<\/p>\n<p>One problem with this approach is that it registers a <code>Changed<\/code> event on the <code>Widget<\/code> even if there is nobody listening to the <code>Dashboard<\/code>&#8216;s <code>Changed<\/code> event. This can be a problem if the <code>Widget<\/code>&#8216;s <code>Changed<\/code> event is expensive to subscribe to. For example, detecting changes to a <code>Widget<\/code> may require the creation of a &#8220;watcher&#8221; object which monitors some system state. If nobody has subscribed to the <code>Dashboard<\/code>&#8216;s <code>Changed<\/code> event, then there&#8217;s no need to run the <code>Widget<\/code> change watcher.<\/p>\n<p>Some people solve this problem by registering on the <code>Widget<\/code>&#8216;s <code>Changed<\/code> event only when somebody registers for the <code>Dashboard<\/code>&#8216;s <code>Changed<\/code> event.<\/p>\n<pre>namespace winrt::Namespace::implementation\r\n{\r\n    struct Dashboard : DashboardT&lt;Dashboard&gt;\r\n    {\r\n        Namespace::Widget const m_widget;\r\n        event&lt;TypedEventHandler&lt;Namespace::Dashboard, IInspectable&gt; m_changed;\r\n        <span style=\"color: #08f;\">std::once_flag m_subscribe_once;<\/span>\r\n\r\n        Dashboard()\r\n        {\r\n            <span style=\"color: red;\">\/\/ <span style=\"text-decoration: line-through;\">m_widget.Changed({ get_weak(), &amp;Dashboard::OnChanged });<\/span><\/span>\r\n        }\r\n\r\n        auto Changed(TypedEventHandler&lt;Namespace::Dashboard, IInspectable&gt; const&amp; handler)\r\n        {\r\n            <span style=\"color: #08f;\">std::call_once(m_subscribe_once, [&amp;]() {\r\n                m_widget.Changed({ get_weak(), &amp;Dashboard::OnChanged });\r\n            });<\/span>\r\n            return m_changed.add(handler);\r\n        }\r\n\r\n        void Changed(event_token token)\r\n        {\r\n            return m_changed.remove(token);\r\n        }\r\n\r\n        void OnChanged(Namespace::Widget const&amp;, IInspectable const&amp;)\r\n        {\r\n            m_event(*this, nullptr);\r\n        }\r\n    };\r\n}\r\n<\/pre>\n<p>This at least delays the registration until needed, but it doesn&#8217;t remove the registration when no longer needed. For that, we would have to add some more synchronization:<\/p>\n<pre>namespace winrt::Namespace::implementation\r\n{\r\n    struct Dashboard : DashboardT&lt;Dashboard&gt;\r\n    {\r\n        Namespace::Widget const m_widget;\r\n        event&lt;TypedEventHandler&lt;Namespace::Dashboard, IInspectable&gt; m_changed;\r\n        <span style=\"color: #08f;\">std::mutex m_lock;\r\n        winrt::event_token m_token{};<\/span>\r\n\r\n        Dashboard()\r\n        {\r\n        }\r\n\r\n        auto Changed(TypedEventHandler&lt;Namespace::Dashboard, IInspectable&gt; const&amp; handler)\r\n        {\r\n            <span style=\"color: #08f;\">std::lock_guard guard(m_lock);\r\n            if (!m_changed) {\r\n                m_token = m_widget.Changed({ get_weak(), &amp;Dashboard::OnChanged });\r\n            }<\/span>\r\n            return m_changed.add(handler);\r\n        }\r\n\r\n        void Changed(event_token token)\r\n        {\r\n            <span style=\"color: #08f;\">std::lock_guard guard(m_lock);<\/span>\r\n            m_changed.remove(token);\r\n            <span style=\"color: #08f;\">if (!m_changed) {\r\n                m_widget.Changed(std::exchange(m_token, {}));\r\n            }<\/span>\r\n        }\r\n\r\n        void OnChanged(Namespace::Widget const&amp;, IInspectable const&amp;)\r\n        {\r\n            m_event(*this, nullptr);\r\n        }\r\n    };\r\n}\r\n<\/pre>\n<p>But there&#8217;s an easier way.<\/p>\n<p>Just get out of the event business entirely, and let the registrations go all the way to the <code>Widget<\/code>. Let the event registration token be the one from the <code>Widget<\/code>&#8216;s <code>Changed<\/code> event. All you have to do is convert the event parameters when the <code>Widget<\/code>&#8216;s <code>Changed<\/code> event is raised.<\/p>\n<pre>namespace winrt::Namespace::implementation\r\n{\r\n    struct Dashboard : DashboardT&lt;Dashboard&gt;\r\n    {\r\n        Namespace::Widget const m_widget;\r\n        <span style=\"color: red;\">\/\/ <span style=\"text-decoration: line-through;\">event&lt;TypedEventHandler&lt;Namespace::Dashboard, IInspectable&gt; m_changed;<\/span><\/span>\r\n\r\n        Dashboard()\r\n        {\r\n        }\r\n\r\n        auto Changed(TypedEventHandler&lt;Namespace::Dashboard, IInspectable&gt; const&amp; handler)\r\n        {\r\n            <span style=\"color: #08f;\">return m_changed.add([weak = get_weak(), handler](auto&amp;&amp;, auto&amp;&amp;)\r\n            {\r\n                if (auto strong = weak.get()) handler(*strong.get(), nullptr);\r\n            });<\/span>\r\n        }\r\n\r\n        void Changed(event_token token)\r\n        {\r\n            <span style=\"color: #08f;\">m_widget.Changed(token);<\/span>\r\n        }\r\n\r\n        <span style=\"color: red;\">\/\/ <span style=\"text-decoration: line-through;\">void OnChanged(Namespace::Widget const&amp;, IInspectable const&amp;)<\/span><\/span>\r\n        <span style=\"color: red;\">\/\/ <span style=\"text-decoration: line-through;\">{<\/span><\/span>\r\n        <span style=\"color: red;\">\/\/ <span style=\"text-decoration: line-through;\">    m_event(*this, nullptr);<\/span><\/span>\r\n        <span style=\"color: red;\">\/\/ <span style=\"text-decoration: line-through;\">}<\/span><\/span>\r\n    };\r\n}\r\n<\/pre>\n<p>When a client wants to register a <code>Dashboard<\/code> <code>Changed<\/code> event handler, we wrap it inside a <code>Widget<\/code> <code>Changed<\/code> event handler and let the <code>Widget<\/code> deal with it. The wrapper captures a weak reference to the <code>Dashboard<\/code> to avoid a circular reference. When the wrapper is called, it recovers the original <code>Dashboard<\/code> from the weak reference so it can use it as the <code>sender<\/code> when calling the client&#8217;s original handler.<\/p>\n<p>This approach gives the <code>Widget<\/code> full insight into the clients, so it can perform whatever optimizations it likes. And it&#8217;s less work for you, too, since you&#8217;ve delegated all the event bookkeeping to the <code>Widget<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It keeps the original object in control.<\/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-107565","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>It keeps the original object in control.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107565","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=107565"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107565\/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=107565"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107565"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107565"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}