The Windows Implementation Library (commonly known as wil) provides a helper called scope_exit which creates and returns an RAII object whose destructor runs the lambda you specify. This helper provides roughly equivalent functionality in C++ to what is called try…finally in other languages such as C#.
// If there is an active primary Gadget, then do a bunch of stuff,
// but no matter what, make sure it is no longer active when finished.
var gadget = widget.GetActiveGadget(Connection.Primary);
if (gadget != null) {
try {
⟦ lots of code ⟧
} finally {
widget.SetActiveGadget(Connection.Primary, null);
}
}
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 GetActiveGadget() call that requires a matching change to the SetActiveGadget(), but since the SetActiveGadget() is so far away, you may not realize that you need to make a matching change 200 lines later.
var gadget = widget.GetActiveGadget(Connection.Secondary); if (gadget != null) { try { ⟦ lots of code ⟧ } finally { widget.SetActiveGadget(Connection.Secondary, null); } }
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.
var gadget = widget.GetActiveGadget(Connection.Secondary);
if (gadget != null) {
try {
⟦ lots of code ⟧
if (gadget.IsEnabled()) {
try {
⟦ lots more code ⟧
} finally {
gadget.Disable();
}
}
} finally {
widget.SetActiveGadget(Connection.Secondary, null);
}
}
Can we get scope_ ergonomics in C#?
You can do it with the using statement introduced in .NET 8 and a custom class that we may as well call ScopeÂExit.
public ref struct ScopeExit
{
public ScopeExit(Action action)
{
this.action = action;
}
public void Dispose()
{
action.Invoke();
}
Action action;
}
Now you can write
var gadget = widget.GetActiveGadget();
if (gadget != null) {
using var clearActiveGadget = new ScopeExit(() => widget.SetActiveGadget(null));
⟦ lots of code ⟧
if (gadget.IsEnabled()) {
using var disableGadget = new ScopeExit(() => gadget.Disable());
⟦ lots more code ⟧
}
}
, Although many objects implement IDisposable so that you can clean them up with a using statement, it’s not practical to have a separate method for every possible ad-hoc cleanup that could be needed, and the ScopeExit lets us create bespoke cleanup on demand.¹
I’m adding this to my list of “Crazy C# ideas which never go anywhere,” joining CastTo<T> and As<T> and ThreadSwitcher.
¹ There might be common patterns like “If you Open(), then you probably want to Close()” which could benefit from a disposable-returning method, but you still have to wrap the result.
public ref struct OpenResult
{
public bool Success { get; init; }
public OpenResult(Widget widget)
{
this.widget = widget;
Success = widget.Open();
}
public void Dispose()
{
if (Success) {
widget.Close();
}
}
private Widget widget;
}
On Windows 9x, what was the use cases for C:\WINDOWS\WINSTART.BAT compared to C:\AUTOEXEC.BAT, and at which phase of the startup process it's launched?
Could we see an explanation of the whole Windows 9x startup and shutdown process one day? On Wikipedia, The 16-bit modules are loaded doesn't tell what precisely technically happens after WIN.COM is launched, besides loading modules.
For example I don't know what module switches to VGA mode in the boot process, draws the busy cursor on black background for 2 seconds and then paints the wallpaper.
AUTOEXEC.BAT was executed by MS-DOS as part of the boot process (the second to last step before launching COMMAND.COM I think). Win 9x shipped on top of MS-DOS 7.x so that meant you got AUTOEXEC.BAT and the full MS-DOS trimmings. Windows NT was built from the ground up to not need MS-DOS so no MS-DOS stuff.
I checked Windows 98 and Windows 3.1 VMs I have, neither have a WINSTART.BAT, and I have not heard of it before despite using Windows since 3.1. Back in those days you just ran WIN.COM to launch Windows, though sometimes you wanted to switch out...
If you want to try (Windows 95), WINSTART.BAT is executed at a later stage in the boot process in protected mode, create a file in C:\WINDOWS\WINSTART.BAT and put COMMAND.COM as the content.
Ive used this type of approach before when writing my own profiler. Start the method with
using (new MyProfiler("LoadData"))records the tick time in the constructor, and it automatically calls the dispose method when scope is exitted, at which point we calculate how many ticks and record.
It rightly is a crazy idea that should never go anywhere, because that type doesn't give the similar level of guarantee as WIL scope_exit.
Allocation of the delegate could fail at the lambda expression. This creates a window where exception can be thrown (I'm ignoring asynchronous thread aborts, which is either impossible in new .NET or can be guarded by CER in .NET Framework) but the scope-exit action is not set up. For non-capturing lambdas, under current optimization, the failure could happen the first time that line is executed. For capturing lambdas, it introduces an allocation each time that line is...