Closed gafter closed 7 years ago
@HaloFour prints 2
, just the same as if you wrote
void Foo() {
int i = 1;
try
{
i = 2;
}
finally
{
Console.WriteLine(i);
}
}
(Unless you get an exception between the two assignments)
Oh, it would just defer to the end of the current block? i somehow thought it deferred to the end of the entire method... :) if its just the current block i think the debugging would be much more reasonable.
When flying over the code above, I think that it might be hard for 3rd party readers to always directly see the defer
statement, esp. when the code block is more complex, has complicated algs, etc.
For a better visual experience I think that you should group the blocks where defer
belongs to, e.g. using the use
keyword:
void Foo() {
use
{
// maybe some more statements
var fileStream = File.OpenWrite(@"C:\users\svick\desktop\test.txt");
}
defer
{
fileStream.Close();
}
}
or shorter:
void Foo() {
use {
var fileStream = File.OpenWrite(@"C:\users\svick\desktop\test.txt");
} defer { fileStream.Close(); }
}
So, the requirement would be that every defer
must follow a directly preceding use
block.
PS: you could use the more verbose do
keyword instead of use
, a lookahead behind the do
-block would tell if it has a loop or a defer meaning.
PPS: adding the defer
statement to the try
statement additionally would then also be of use. With try
exceptions can also caught by catch
blocks, but in contrast to finally
those blocks are always excecuted when leaving the function.
void Foo() {
try {
var fileStream = File.OpenWrite(@"C:\users\svick\desktop\test.txt");
}
catch (IOException e) {
Debugger.Log(1, "FileErrors", "Cannot open test.txt");
return;
}
defer { // outer block for defer is the Foo method
if (fileStream != null) fileStream.Close();
}
// try block already ended, but fileStream is still open
ReadFile(fileStream);
}
(I know that the sample doesn't make much sense and there are other, better ways, it's just a coding sample ;-)
Another thought on defer
.
The real win here is that it can be used for things that are not IDisposable
. And while there was a comment earlier from @HaloFour that he sees no validity in the argument, defer is not limited to resources that must be disposed, nor is every resource or operation that requires some finalization always surface a Dispose()
.
Defer instead introduces a new construct that can be used not only for releasing resources, but to ensure that certain operations take place before the code completes.
@gafter already provided a common idiom from the Roslyn compiler, but this is not limited to Roslyn, the idiom "var saved = GetState (); ChangeState (newstate); DoOperation (); RestoreState (saved)" is common.
Do not take my word for it, a search here, shows very interesting uses of defer and they are not all bound to releasing resources:
@migueldeicaza
Quickly scanning those examples I can easily see that the vast majority of cases are Swift's reimplementations of using
or lock
. Surprisingly, much more the latter than the former. Of the remaining cases I mostly see bizarre ways of injecting mutation logic post return
, e.g.:
mutating func unsafePop() -> UTF8.CodeUnit {
defer { pointer = pointer.advancedBy(1) }
return pointer.memory
}
I frankly don't see how that's more readable than the equivalent:
mutating func unsafePop() -> UTF8.CodeUnit {
let result = pointer.memory
pointer = pointer.advanceBy(1)
return result
}
Sure, one less line of code, which buys you out-of-lexical-order execution of sequential statements.
I don't doubt that there are some really good novel uses for a statement like defer
. I'm not seeing them in those examples. Nor do I think that the rare occasion where it might be useful warrants a language feature that is effectively an alias for try
/finally
. The idea of having to mentally keep track of implicit scope popping behavior when reading code does not appeal to me. Having to know when to read code backwards does not appeal to me. Encouraging people to write disposable resources that spurn the 15 year old established disposable pattern does not appeal to me.
@migueldeicaza, @HaloFour I suspect by far the most common use case for a defer statement in C# would be lock management structures like ReaderWriterLockSlim
changing code like this to:
public class Set<T>
{
private readonly HashSet<T> set = new HashSet<T>();
private readonly ReaderWriterLockSlim readerWriterLockSlim = new ReaderWriterLockSlim();
public bool Add(T value)
{
readerWriterLockSlim.EnterReadLock();
{
defer { readerWriterLockSlim.ExitReadLock(); }
if(set.Contains(value))
return false;
}
readerWriterLockSlim.EnterWriteLock();
defer { readerWriterLockSlim.ExitWriteLock(); }
return set.Add(value);
}
public bool Remove(T value)
{
readerWriterLockSlim.EnterWriteLock();
defer { readerWriterLockSlim.ExitWriteLock(); }
return set.Remove(value);
}
public T[] GetValues()
{
readerWriterLockSlim.EnterReadLock();
defer { readerWriterLockSlim.ExitReadLock(); }
return set.ToArray();
}
}
Here, the necessary naked block in the Add
method makes me more uneasy about the thought of a defer statement than anything else I've seen so far. Other tasks I'd imagine are transaction commits.
Also, how should it play with a switch
statement?
void Foo(int state)
{
int i = 1;
{
switch (state)
{
case 0: defer {i++;} goto case 1;
case 1: defer {i *= 2;} break;
case 2: defer {i = 3;} goto case 1;
default: defer {i = 5;} break;
}
}
Console.WriteLine(i);
}
Or yield
or await
?
This prints a 0 b 1
because in Swift each case body has its own block.
let state = 0
switch (state) {
case 0: defer {print("0")}; print("a"); fallthrough
case 1: defer {print("1")}; print("b");
case 2: defer {print("2")}; print("c");
default: defer {print("3")}; print("d");
}
No idea how it should work in C# though.
Issue moved to dotnet/csharplang dotnet/roslyn#513 via ZenHub
Swift recently added the
defer
statement. Would that make sense for C# and VB? in C# I imagine it would look something likeThe idea is that the code in the defer block would be executed as the last action in the enclosing block. This would be the same as writing