{"id":112008,"date":"2026-01-23T07:00:00","date_gmt":"2026-01-23T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112008"},"modified":"2026-01-23T08:48:52","modified_gmt":"2026-01-23T16:48:52","slug":"20260123-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260123-00\/?p=112008","title":{"rendered":"C++ has <CODE>scope_exit<\/CODE> for running code at scope exit. C# says &#8220;We have <CODE>scope_exit<\/CODE> at home.&#8221;"},"content":{"rendered":"<p>The <a href=\"http:\/\/github.com\/microsoft\/wil\"> Windows Implementation Library<\/a> (commonly known as wil) provides a helper called <code>scope_exit<\/code> which creates and returns an RAII object whose destructor runs the lambda you specify. This helper <a title=\"All the other cool languages have try...finally. C++ says &quot;We have try...finally at home.&quot;\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20251222-00\/?p=111890\"> provides roughly equivalent functionality in C++ to what is called <code>try<\/code>&#8230;<code>finally<\/code> in other languages<\/a> such as C#.<\/p>\n<pre>\/\/ If there is an active primary Gadget, then do a bunch of stuff,\r\n\/\/ but no matter what, make sure it is no longer active when finished.\r\nvar gadget = widget.GetActiveGadget(Connection.Primary);\r\nif (gadget != null) {\r\n    try {\r\n        \u27e6 lots of code \u27e7\r\n    } finally {\r\n        widget.SetActiveGadget(Connection.Primary, null);\r\n    }\r\n}\r\n<\/pre>\n<p>One thing that is cumbersome about this pattern is that the cleanup code is far away from the place where the cleanup obligation was created, making it harder to confirm during code review that the proper cleanup did occur. Furthermore, you could imagine that somebody makes a change to the <code>GetActiveGadget()<\/code> call that requires a matching change to the <code>SetActiveGadget()<\/code>, but since the <code>SetActiveGadget()<\/code> is so far away, you may not realize that you need to make a matching change 200 lines later.<\/p>\n<pre>var gadget = widget.GetActiveGadget(<span style=\"border: solid 1px currentcolor;\">Connection.Secondary<\/span>);\r\nif (gadget != null) {\r\n    try {\r\n        \u27e6 lots of code \u27e7\r\n    } finally {\r\n        widget.SetActiveGadget(<span style=\"border: solid 1px currentcolor;\">Connection.Secondary<\/span>, null);\r\n    }\r\n}\r\n<\/pre>\n<p>Another thing that is cumbersome about this pattern is that you may create multiple obligations at different points in the code execution, resulting in deep nesting.<\/p>\n<pre>var gadget = widget.GetActiveGadget(Connection.Secondary);\r\nif (gadget != null) {\r\n    try {\r\n        \u27e6 lots of code \u27e7\r\n        if (gadget.IsEnabled()) {\r\n            try {\r\n                \u27e6 lots more code \u27e7\r\n            } finally {\r\n                <span style=\"border: solid 1px currentcolor;\">gadget.Disable();<\/span>\r\n            }\r\n        }\r\n    } finally {\r\n        <span style=\"border: solid 1px currentcolor;\">widget.SetActiveGadget(Connection.Secondary, null);<\/span>\r\n    }\r\n}\r\n<\/pre>\n<p>Can we get <code>scope_<wbr \/>exit<\/code> ergonomics in C#?<\/p>\n<p>You can do it with the <code>using<\/code> statement introduced in .NET 8 and a custom class that we may as well call <code>Scope\u00adExit<\/code>.<\/p>\n<pre>public ref struct ScopeExit\r\n{\r\n    public ScopeExit(Action action)\r\n    {\r\n        this.action = action;\r\n    }\r\n\r\n    public void Dispose()\r\n    {\r\n        action.Invoke();\r\n    }\r\n\r\n    Action action;\r\n}\r\n<\/pre>\n<p>Now you can write<\/p>\n<pre>var gadget = widget.GetActiveGadget();\r\nif (gadget != null) {\r\n    using var clearActiveGadget = new ScopeExit(() =&gt; widget.SetActiveGadget(null));\r\n    \u27e6 lots of code \u27e7\r\n    if (gadget.IsEnabled()) {\r\n        using var disableGadget = new ScopeExit(() =&gt; gadget.Disable());\r\n        \u27e6 lots more code \u27e7\r\n    }\r\n}\r\n<\/pre>\n<p>, Although many objects implement <code>IDisposable<\/code> so that you can clean them up with a <code>using<\/code> statement, it&#8217;s not practical to have a separate method for every possible ad-hoc cleanup that could be needed, and the <code>ScopeExit<\/code> lets us create bespoke cleanup on demand.\u00b9<\/p>\n<p>I&#8217;m adding this to my list of &#8220;Crazy C# ideas which never go anywhere,&#8221; joining <a title=\"Controversial extension methods: CastTo&lt;T&gt; and As&lt;T&gt;\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20191227-00\/?p=103271\"> <code>CastTo&lt;T&gt;<\/code> and <code>As&lt;T&gt;<\/code><\/a> and <a title=\"C++\/WinRT envy: Bringing thread switching tasks to C# (UWP edition)\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190328-00\/?p=102368\"> <code>ThreadSwitcher<\/code><\/a>.<\/p>\n<p>\u00b9 There might be common patterns like &#8220;If you <code>Open()<\/code>, then you probably want to <code>Close()<\/code>&#8221; which could benefit from a disposable-returning method, but you still have to wrap the result.<\/p>\n<pre>public ref struct OpenResult\r\n{\r\n    public bool Success { get; init; }\r\n\r\n    public OpenResult(Widget widget)\r\n    {\r\n        this.widget = widget;\r\n        Success = widget.Open();\r\n    }\r\n\r\n    public void Dispose()\r\n    {\r\n        if (Success) {\r\n            widget.Close();\r\n        }\r\n    }\r\n\r\n    private Widget widget;\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>You can wrap it in an <CODE>IDisposable<\/CODE>.<\/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-112008","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You can wrap it in an <CODE>IDisposable<\/CODE>.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112008","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=112008"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112008\/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=112008"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112008"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112008"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}