Best known for its use in Go and Swift, C# proposal #1398 seeks to add defer statements. If you are not familiar with the concept, it can be summarized as a finally block appearing at the beginning of some code instead of the end.
Here is an example from the proposal:
{
SomeType thing = Whatever...;
defer {
thing.Free();
}
// some code code using thing
}
This would be interpreted as:
{
SomeType thing = Whatever...;
try
{
// some code code using thing
}
finally
{
thing.Free();
}
}
As it does so closely resemble a finally block, many developers think the syntax is redundant. Neal Gafter of Microsoft presents a counter-argument by claiming it has these advantages:
- They do not require implementing some interface, such as IDisposable, or creating helper objects that do.
- They do not add an indentation level to the code, which would be clumsy when there are more than one.
- They do not move the cleanup code to the end, as try-finally does, but rather they keep the cleanup code logically with the declaration and initialization of the thing that is being cleaned up. In this way they make the code easier to understand.
Alireza Habibi rejects this with:
I don't think that the real problem with using statement is additional indentions nor that you have to implement an interface — this is not a bad thing at all, so there is a contract for types that need cleanup. I think the actual problem here is that you might forget to dispose disposable types; defer statement doesn't help with this at all, it rather encourages you to not implement IDisposable interface and explicitly call methods like Close or Free which in presence of IDisposable seem like code smells.
Sam also questions the new keyword:
If the use case for defer is resource cleanup, then it would seem to be an anti-pattern to me to not require that the thing implement IDisposable. Implementing IDisposable allows tooling to warn you if you create a disposable as a local variable and then never dispose of it. If you make it accepted for resources to not require IDisposable, you lose this benefit.
Another complaint is it makes the order of operations hard to understand. A user going by the handle HaloFour offers this example:
static void Main() {
Console.WriteLine("Hello"); //1
defer {
Console.WriteLine("Foo"); //5
}
defer {
Console.WriteLine("Bar"); //4
}
defer {
Console.WriteLine("Baz"); //3
}
Console.WriteLine("World"); //2
}
For your convenience, the order in which the lines will be executed have been added as comments. As you can see, the code is executed from top to bottom, skipping some lines, and then from bottom to top.
Exception Handling
One question discussed is what will the effect of an exception in a defer block will be. With a normal try-finally, the current finally block is aborted but other finally blocks that wrap it will still be executed. Would that be the case for defer or would the first failure cause the others to be bypassed? The general consensus seems to be the remaining defer blocks will still be executed.
Swift avoids this issue by not allowing code that can throw an exception to be called from within a defer block.
The defer statement proposal is currently tagged as a C# 8.x candidate, but that doesn’t mean it was actually chosen to be part of a future C# release. Its official status is “proposal champion”, which means a member of the C# team is interested in representing the feature in a future language design meeting (LDM).
Community comments
Maybe but probably not
by Sam King,
I do not like it
by Obinna Okafor,
using
by Michael Robin,
What utter nonsense!
by Steve Naidamast,
Using as defer.
by Nikolay Bobrovskiy,
Maybe but probably not
by Sam King,
Your message is awaiting moderation. Thank you for participating in the discussion.
With using declarations (blogs.msdn.microsoft.com/dotnet/2019/01/24/do-m...) in C# 8.0, it seems far less likely to me that `defer` will ever happen. And good riddance, I say.
I do not like it
by Obinna Okafor,
Your message is awaiting moderation. Thank you for participating in the discussion.
This should not be allowed. It is an anti-pattern
using
by Michael Robin,
Your message is awaiting moderation. Thank you for participating in the discussion.
We already have using for this case - and now the syntax has been eased up a bit, so you can do:
using var foo = new Foo();
And IDispose will already be called when "foo" goes out of scope.
If you wanted to extend this to items that were not IDisposable, we should keep it at declaration-time like so:
using var foo = new Foo() with IDisposable { this.Close(); }
Note that we do not execute any "deferred" statements for vars that may not have been initialized yet.
This would be done as shown above with a new C# version of the F# "object expressions" which allow you to create instances with overwritten interface methods on-the-fly. This feature is also generally useful other contexts as well so would not just be a feature to address "defer"-like functionality.
If you didn't want to implement the general object expressions, you could use a special syntax just for dispose:
using var foo = new Foo() dispose with {this.Close();}
What utter nonsense!
by Steve Naidamast,
Your message is awaiting moderation. Thank you for participating in the discussion.
The developers have nothing better to do than come up with this stupidity?
Using as defer.
by Nikolay Bobrovskiy,
Your message is awaiting moderation. Thank you for participating in the discussion.
I agree with others that C# 8.0 "using" should be enough.
using _ = new Defer(() => {/*Defer action*/})
class Defer : IDisposable {
Action act;
public Defer(Action act) {this.act = act;}
pubilc Dispose() {this.act();}
}