-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
"defer" statement #8115
Comments
@gafter I'd have to see use cases that aren't already sufficiently met through |
ِDoesn't RAII (#181) address this? {
using Disposable thing = ...;
// disposed
} Because that kind of operations for a type without being a However for this to work it should be able to borrow the object, or totally pass the ownership, void F(Disposable d) { ... } // won't dispose as always
void G(using Disposable d) { ... } // will dispose at the end
{
using Disposable d = ...;
F(d); // d is borrowed
// d is available
G(d); // pass the ownership (move)
// d is not available
} This probably needs a sophisticated ownership system (then we might be able to use Related: dotnet/csharplang#6611, #161. |
This reminds me of the defer attribute for the script tag in HTML. It allows the browser to parallel the download of the script while continue parsing html. I don't see why this is useful in C#. |
It's possible to just simulate this using Disposable.Create from Rx: using (Disposable.Create(DoSomething))
{
DoSomethingElse();
} // end of scope, DoSomething is called here As such I don't think a |
The advantages of the
|
@gafter Hm, I'm not sure. I was skeptical at first, but the arguments you presented are good. If we're going to go down that route, we may want to consider adding an option for writing it inline like you can in Go, e.g. var resource = AcquireResource();
defer resource.Dispose(); // no braces |
Still not sold on that! |
If the purpose of var resource = new MyNonDisposableResource() using resource.Close(); |
@gafter I don't think that real problem with |
@alrz Using |
PowerShell has the trap statement, which has similar usage to the defer statement. The trap statement is little confusing and I prefer the try-catch statement. |
@HaloFour If we ever wanted it to be the default behavior but yes, then other languages or even earlier versions of C# might be considered as unsafe. But as I said in my first comment here, RAII (using |
@alrz Changing the keywords doesn't change how difficult it may or may not be. And if developers still need to opt-into it then you still haven't solved any of the problems. At least with destructible types it was a property of the type and the consumer didn't have to opt-into anything. RAII doesn't imply anything about ownership. The convention as it was developed in C++ relies on the lifetime of the object being tied to the stack of the method within which it was created. #181 is a much closer implementation of that than anything involving ownership, reference counting or move semantics. |
@HaloFour This is more related to dotnet/csharplang#6611 and #161 and somehow covering #181 but probably out of scope of this proposal, so I just give it up. 😄 |
If the the use case for I think method scoped |
I see lots and lots of code like this in Roslyn: var oldMethodOrLambda = this.currentMethodOrLambda;
this.currentMethodOrLambda = node.Symbol;
... // process the current lambda
this.currentMethodOrLambda = oldMethodOrLambda; I would hate to use IDisposable for this. var oldMethodOrLambda = this.currentMethodOrLambda;
this.currentMethodOrLambda = node.Symbol;
defer this.currentMethodOrLambda = oldMethodOrLambda;
... // process the current lambda |
@gafter: That is really dynamic binding. It would be nice to do syntax sugar for what I did in code here http://www.codeproject.com/Articles/153896/Dynamic-Binding-in-C |
I would like to note that D has had the scope guard statement for some time, but it allows you to do something when scope fails, or succeeds as well. Anyway wouldn't this be more useful as a public interface IScopeGuard
{
void Failed(Exception ex);
void Success();
}
public class Transaction : IScopeGuard, IDisposable
{
private readonly int id;
public Transaction()
{
id = BeginTransaction();
Console.WriteLine($"Started transaction {id}");
}
void Failed(Exception ex)
{
FailTransaction(id);
}
void Success()
{
CommitTransaction(id);
}
void Dispose()
{
Console.WriteLine($"Transaction {id} completed.");
}
}
using(new Transaction())
{
DoTransactionalStuff();
} edit: it occurs to me that this is a digression. Sorry about that. |
There is great talk about implementing this feature in C++ by Andrei Alexandrescu. Some string arguemnts in favor of this there. |
@gafter That use case is really similar to what block(this.currentMethodOrLambda = node.Symbol)
{
} Then there is no need for temporary variable and explicit As an alternative I'd suggest scoped assignments, {
scoped this.currentMethodOrLambda = node.Symbol;
} But it seems that this is useful only for this specific use case. |
@gafter But that only works if there is no other logic you want to come after the restoration of |
@MgSam Agreed. For the most simple cases that may be suitable, otherwise you end up having to defer multiple operations and be extremely conscious as to the fact that they'll execute backwards lexically. This task is pretty easily accomplished through var oldMethodOrLambda = this.currentMethodOrLambda;
try
{
this.currentMethodOrLambda = node.Symbol;
// ... process the current lambda
}
finally {
this.currentMethodOrLambda = oldMethodOrLambda;
} |
I'm sorry @gafter, but I'm still failing to see the value of this proposal. |
What is the order of multiple defer blocks, and what if exceptions throw in the middle? |
@DiryBoy If it were to be implemented as it is in Swift, the following C# code: static void Main() {
Console.WriteLine("Hello");
defer {
Console.WriteLine("Foo");
}
defer {
Console.WriteLine("Bar");
}
defer {
Console.WriteLine("Baz");
}
Console.WriteLine("World");
} would produce the following output:
As far as I can tell it's illegal to throw from within the |
It's like a |
I love the idea of the It provides an expressive way to state the programmer's desired intention. While try/finally may accomplish the same work, it pushes the intention far away, and in nested cases becomes inconvenient to navigate. |
I think |
I wouldn't recommend 'mixing' the execution order of the code with For the use-case @gafter mentioned I think it is besser to leave the assignment |
You can mock this up with an IDisposable stack of delegates. Instead of "defer", push a lambda closure to the stack. Inside Dispose(), run them in reverse order. That's a lot of overhead for reaching a finally block that's usually less than a Page Down away. It feels pointlessly clever. The problem with a struct implementing IDisposable to solve this pattern everywhere is that Dispose() will cause it to be boxed. We need a pattern for the using-statement, not an interface, using method names the compiler knows about, exactly like when I build awaiter/awaitable objects. |
@playsomethingsaxman
No, it won't. When you you use |
Niiice, learn something new every day. Not sure what we really need then. |
@playsomethingsaxman
I suggest you rewrite the original post using your suggested mechanism. Don't modify existing types; if you need to declare a new struct, include it in your solution. Is it easier to read? |
I can't modify existing types but I can create extension methods? Game on. But at some point a .NET-facing library has to do its part and accommodate basic .NET patterns. To simulate what "defer" really is...
To me that would be a nightmare to step through and debug, especially with GULP locks. Here's another question: what happens to the remaining deferrals if the first one throws and all of them are guaranteed to run? Do we assemble an |
Yes, the alternative is pretty bad. Yet another reason to support the original proposal. |
The Given that it is only a compiler trick and should require no run-time support, I think this is a fantastic addition to nearly any language. Kudos to the devs at Swift for inventing it, and to the devs here on Roslyn for considering it. Only question: what happens if a |
Another observation/question: does it make more sense for the sequence of
Should it be:
Or
As I understand it, with Swift's implementation the former would be the case, however the later makes more sense to me as that was the order it was declared. Thoughts?
I agree, but |
@whoisj Since the prototypical use case seems to be resource cleanup I think that reverse order makes more sense. The compiler can't know if cleaning up the first resource might depend on cleaning up subsequent resources. Also, every language I've found with this facility seems to invoke in reverse order, including D, Swift, Go, Rust (via |
@whoisj Just like with using (var fileStream = File.OpenWrite(@"C:\users\svick\desktop\test.txt"))
using (var gZipStream = new GZipStream(fileStream, CompressionMode.Compress))
using (var writer = new StreamWriter(gZipStream))
{
writer.WriteLine("Hello World");
} The natural way to rewrite it with var fileStream = File.OpenWrite(@"C:\users\svick\desktop\test.txt");
defer { fileStream.Close(); }
var gZipStream = new GZipStream(fileStream, CompressionMode.Compress);
defer { gZipStream.Close(); }
var writer = new StreamWriter(gZipStream);
defer { writer.Close(); }
writer.WriteLine("Hello World"); This would only work correctly if |
@svick good point! Explains the decision Swift made. The As for RAII, I'd love to see support for it. Sadly, I believe that would require more than compiler ticks to implement correctly and CLR/IL changes are pretty much out of scope. |
To me this sounds like a feature that would add alot of complexity and opportunity for misuse for not a lot of benefit. I assume defer would only work inside a perticular method, but resource cleanup is commonly done at a later time, say when the object itself is cleaned up. I do see the charm, but i just don't think it adds enough to the language to warrant a new keyword. Also, what is the closure situation? Consider
Would If defer statements can contain anything, they can also change state i assume, so this would be legal:
A bit contrived for sure, but my point is that somewhere half a page up, in some branch something is defered and that might be hard to keep track of. The execution order will jump all over the place (and i guess thats the point) but it will be hard to get an overview, i sort of have to keep stack in my head with all the deferals :) |
We might be able to do this with dotnet/csharplang#2478 and code generators, marking the node we want to defer with a attribute and regenerate the syntax tree, void Method() {
var something = new Something();
[[Defer]] something.Free();
// blah
}
replace void Method() {
var something = new Something();
try {
// blah
} finally {
something.Free();
}
} However, I'm not aware if generator API allows to |
Since for(var i = 0; i<10; i++) {
Something(i);
} Defer would not be an embedded-statement, so you could not use it as the controlled statement of an |
I think more to the point, what would the behavior of the following be? void Foo() {
int i = 1;
defer Console.WriteLine(i);
i = 2;
} |
@HaloFour prints 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 For a better visual experience I think that you should group the blocks where 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 PS: you could use the more verbose PPS: adding the 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 The real win here is that it can be used for things that are not 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: |
Quickly scanning those examples I can easily see that the vast majority of cases are Swift's reimplementations of 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 |
@migueldeicaza, @HaloFour I suspect by far the most common use case for a defer statement in C# would be lock management structures like 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 Also, how should it play with a 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 |
This prints 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
The text was updated successfully, but these errors were encountered: