Replies: 102 comments
-
How would this feature interact with goto statement?
This could write foo 10 times, but how will compiler generate the code for this? I guess mixing goto's and defer could either be disallowed. Simillarly following block of code:
Could write either one foo or none, but again this seems problematic for the compiler. |
Beta Was this translation helpful? Give feedback.
-
@ghord Does not matter, the It would not legal to jump across blocks like:
|
Beta Was this translation helpful? Give feedback.
-
@leppie So you are basically proposing that
should still print to the console, even when defer statement is not reachable? This may seem a little counter-intuitive. |
Beta Was this translation helpful? Give feedback.
-
I think that a deferred block should only be executed, when leaving the method, when it's corresponding |
Beta Was this translation helpful? Give feedback.
-
@lachbaer I believe the assumption is when the current scope expires, the |
Beta Was this translation helpful? Give feedback.
-
@leppie no, because the |
Beta Was this translation helpful? Give feedback.
-
@ghord it would be similar to how I implemented dynamic binding for C# in https://www.codeproject.com/Articles/153896/Dynamic-Binding-in-C The syntax sugar would imply everything in the block except the So something like this would be possible:
which is just
|
Beta Was this translation helpful? Give feedback.
-
@whoisj I dont quite agree with those semantics. Are you saying:
should not print? That seems counter-intuitive, but perhaps thats the weird way people want it.... Edit: I guess it would not be that hard to exclude non-reachable code from being added to 'finally'. Undecided. |
Beta Was this translation helpful? Give feedback.
-
@leppie I am, but I'm no authority here. I understand For example ... bool error = false;
if (somethingtrue)
{
goto skip;
defer error = true;
skip: ;
}
Console.WriteLine(error); // prints true ... would be equivalent to ... bool error = false;
if (somethingtrue) {
var deferred = new Stack<Action>();
try {
goto skip;
deffered.Push(()=>{ error =true; });
skip: ;
} finally {
foreach (var action in deferred) {
action();
}
}
}
Console.WriteLine(error); // prints false |
Beta Was this translation helpful? Give feedback.
-
@whoisj While that is a semantically correct implementation, it is something I would rather not see the compiler does. Unreachable code is easy to detect, but branching would make this 'likely' impossible to implement in a more efficient way. I dont really see any benefit it would add to the language. Maybe @gafter can do a magic state machine transform (like PS: Nice edit :D |
Beta Was this translation helpful? Give feedback.
-
@leppie if all if (something) {
var thing = new Thing();
defer thing.Dispose();
} else if (condition) {
var foo = new Foo();
defer foo.Dispose();
} else {
var thing = new Bar();
defer thing.Release();
} Which |
Beta Was this translation helpful? Give feedback.
-
@whoisj Your example does not really explain anything. All the But yes, I get what you are saying, things will get hairy very quickly. It is nice for the And implementation could be as simple as (using shorthand):
Usage:
|
Beta Was this translation helpful? Give feedback.
-
The only example of a language that I've found that includes both func main() {
goto done
defer fmt.Println("foo");
done:
} That does not print So, I'd say that the equivalent C# shouldn't either. Afterall, that condition likely sets up whatever would need to be deferred. And while So, given: {
goto done;
defer Console.WriteLine("foo");
done:
} I'd expect it to emit the equivalent to the following: {
bool $temp = false;
try {
goto done;
$temp = true;
done:
}
finally {
if ($temp) Console.WriteLine("foo");
}
} |
Beta Was this translation helpful? Give feedback.
-
@HaloFour I like your implementation idea, it is much simpler than I thought, and will probably work for 'all' cases I can think of now. If that is the case, then it would a valuable language addition. IOW, precisely what a compiler should do. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour that's exactly what I meant by my #513 (comment)! 😃 The only thing I've mistaken was that Btw, wouldn't it be wise to (optionally) extend that block to the scope of a variable? StreamReader sr = null;
if ([...]) {
sr = new StreamReader([...]);
defer (sr) { if (sr != null) sr.Close(); }
} |
Beta Was this translation helpful? Give feedback.
-
Yes it does. It allows you to what you could before in a way that avoids the problems of the existing solutions. That's precisely why it's being discussed. It's like saying: you don't need lambdas! You could just create callback classes and pass those around! Yes, it's pedantically true (and yes, people made that argument when we introduced lambdas). But it entirely missed the point. yes, i totally could write: var underage = customers.Where(new CustomerAgeLessThanPredicate(21));
...
class CustomerAgeLessThanPredicate : IPredicate<Customer> {
private readonly int _age;
public CustomerAgeLessThanPredicate(int age) => _age = age;
public bool Test(Customer c) => c.Age < _age;
} but that was unpleasant and onerous. The approaches possible today are similar in their problems. And this is something i feel at least weekly as i bounce around these languages. It's just one of those areas where you come back to C# and go "man... this feel so well handled elsewhere, it's a pity they have a half-solutoin today, since it's preventing them from just adopting the better solutoin found elsewhere". |
Beta Was this translation helpful? Give feedback.
-
Ok. Let's start with something simple. How would you make your suggestion work when the lambda needs to capture something that it's not allowed to capture today (like say, a 'Span'). How do you make it so that we can do something with that span at the end of the scope? |
Beta Was this translation helpful? Give feedback.
-
That is literally not what i said. I said i want a solution that does not have the limitations of that approach. :) You literally snipped out that part of the response (i'm going to presume that was not done intentionally). :)
The reusable pattern you are suggesting literally will not work for cases where i want to use defer. it's the same reason why you don't see people writing:
either today. Closures are limited in C#. They're not a general all-purpose substitute for normal control flow logic. Furthermore, it's very un-C#-y to code in this manner. It's not the way that the language has every positioned itself, and it's not found commonly in the ecosystem. Taking a tiny feature and saying it shouldn't be necessary because people coudl just code in a massively different fashion than today and hoping that if the language can somehow undo the limitations inherent in your alternate proposals, is basically a non-starter. Same as how it's a non-started to put forth the idea that C# will just get metaprogramming, and thus won't need language features anymore since they'll all just run through the metaprogramming language. These approaches are pretty much DOA. So i'm focusing on simple, pragmatic, approaches that can slot into the language trivially, and which can solve these problems realistically, without all the caveats of the alternative heavier approaches. |
Beta Was this translation helpful? Give feedback.
-
Any syntax change is a multi-million dollar and years-long change down the chain of client IDEs and plug-ins, web-based IDEs and colorizers, code analysis tools, etc. Again, to me, very not worth it in this case. Efficiency can be a concern - but: A matter of personal experience I guess -- but when writing C# I would much rather have a bracketless try/catch/finally expression than a defer keyword - the former I come across all the time, while I don't recall ever being unhappy with "using" to the point I would mind hand-coding any special cases. |
Beta Was this translation helpful? Give feedback.
-
This is probably a stupid question, but... if: // before defer
defer { /* in defer */ }
// after defer gets translated to: // before defer
try
{
// after defer
}
finally
{
/* in defer */
} then could I do this?: {
RuntimeHelpers.PrepareConstrainedRegions();
defer {
// constrained region
}
} ? 😄 |
Beta Was this translation helpful? Give feedback.
-
Wat? As someone who has worked in the Roslyn codebase for years, i can tell you that that is not at all true. This specific feature would be on the order of a couple of man-days (mostly around just writing the tests). There would also be some additional time i would take to add new IDE features to suggest changing compatible code to this form. But that's an optional piece of work that wouldn't be part of the core cost of this feature.
Absolutely none of those concerns were things i brought up. They're effectively a strawman. :)
This doesn't address a core value-prop of I brought this up earlier, but First, start with: // start of scope code
try
{
// code
}
finally
{
// end of scope code
} Step 1: move the
Step 2: remove the
Step3: rename 'finally' to 'defer' to read better:
That's really it. It's something with all the power of try/finally, but without the annoyances of it. Honestly, i think if hte keyword were 'finally', people would not be complaining so much. but, 'finally' doesn't really make sense as a standalone fragment like |
Beta Was this translation helpful? Give feedback.
-
From teh language's perspective, CERs aren't a thing. It's like asking if you can use a |
Beta Was this translation helpful? Give feedback.
-
If you're just talking about MSFT work or work on the local toolchain, sure - but that's not the whole world. (And there are still plenty of tools that use YACC,etc., parser-gen tools or hand-rolled recursive descent parsers and not Roslyn because C# is only 1 of N languages that need to be supported.) Anyway - point taken - I'm not saying it's hard to implement in and of itself. I'm a huge fan of the power that C# gives me, including new syntaxes like span in combination with BCL and user code, (over Java, let's say) and I promote C# and F# all I can.
If it's there, feel free to champion your own name :) [Edit: Although @aKzenT makes a good point as well] |
Beta Was this translation helpful? Give feedback.
-
@yaakov-h According to my understanding of it, yes, I would expect that to work. Many of us just want |
Beta Was this translation helpful? Give feedback.
-
15 year old post, still relevant in my eyes: The cost of adding a feature is not only the development time. Just to mention a few:
For me at least it doesn't has high enough value to outweigh the cost, but as @thelazydogsback said, you feel very strongly about it, so maybe we are missing some of the benefits you see. As for wording, I would actually prefer to have the keyword be "finally" instead of "defer". In my eyes it would at least make the feature easier to understand for existing C# users. It really is a finally-block that is not bound to a try-block but to the following code within the parent scope. More or less similar to the way the new using-declarations work. And it avoids confusion with the defer statement in other languages which seem to work quite differently. |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi |
Beta Was this translation helpful? Give feedback.
-
It is the language designers that determine the points, though. All it takes is a champion to get it into the meetings for consideration and enough of a consensus that it's worth the effort to implement. |
Beta Was this translation helpful? Give feedback.
-
Tiny scope, low effort is a great way to quickly gain points. |
Beta Was this translation helpful? Give feedback.
-
Regardless of my scepticism with this feature, I'm not really a fan of the naming of the "defer" keyword itself either. I could imagine using "finally" without a corresponding "try"-block, but it could in some cases not be directly obvious if there is a "try"-block or not. Browsing arround today I noticed that D uses the scope-keyword http://ddili.org/ders/d.en/scope.html and it even seems to be more similar to the proposal here than the defer-statement in swift/go (being tied to the scope instead of the enclosing function). So that got me thinking, why not do something similar in C#:
So the finally would mean the same as always and the scope-keyword would make it clear that it is attached to the surrounding scope instead of some try-block. It would also open up the possibility to extend this to failure cases later like it is possible with D's scope(failure):
What do you think? |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
@gafter commented on Fri Jan 22 2016
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
@HaloFour commented on Fri Jan 22 2016
@gafter I'd have to see use cases that aren't already sufficiently met through
try
/finally
orusing
. It doesn't even seem that Apple could provide a worthwhile example of why you'd use it.@alrz commented on Sat Jan 23 2016
ِDoesn't RAII (#181) address this?
Because that kind of operations for a type without being a
IDisposable
isn't idiomatic C#.However for this to work it should be able to borrow the object, or totally pass the ownership,
This probably needs a sophisticated ownership system (then we might be able to use
let
instead).Related: #160, #161.
@HaloFour commented on Fri Jan 22 2016
@gafter According to NSHipster the
defer
statement should be avoided for contexts other than ensuring resource cleanup.I'd say that
defer
is Swift's answer tousing
and my opinion is that C# doesn't need a rebuttal or to copy a feature that can be as potentially misused/abused as this could be.@DiryBoy commented on Fri Jan 22 2016
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#.
@jamesqo commented on Sat Jan 23 2016
It's possible to just simulate this using Disposable.Create from Rx:
As such I don't think a
defer
statement is needed in C#.@gafter commented on Sun Jan 24 2016
The advantages of the
defer
statement over the alternatives areIDisposable
, or creating helper objects that do.@jamesqo commented on Sun Jan 24 2016
@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.
@paulomorgado commented on Sun Jan 24 2016
Still not sold on that!
@HaloFour commented on Sun Jan 24 2016
@gafter
IDisposable
is the pattern for creating resources which should be cleaned up then I don't see much validity to this argument. Endorsing an additional pattern, or no pattern at all, would only add confusion.using
to support convention-based disposing rather than strictly requiringIDisposable
, such as supporting objects that declare an instanceClose()
method.using var disposable = new SomeDisposableObject();
Same mechanism but the resource is attached to the defining scope, no additional indentation required.IDisposable
, which you should be using anyway.If the purpose of
defer
is to provide resource cleanup I'd rather find ways of improving uponusing
in a structured way rather than to toss in a completely new language construct with massive abuse potential which appears to have no real use cases aside resource cleanup. To toss some spaghetti against the wall, how about the following:@alrz commented on Sun Jan 24 2016
@gafter I don't think that 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 implementatIDisposable
inetrface and explicitly call methods likeClose
orFree
which in presence ofIDisposable
seem like code smells.@HaloFour commented on Sun Jan 24 2016
@alrz Using
let
as you describe seems like it would be in conflict with #6400 which is proposing thatlet
have a completely different function. Blending the two wouldn't make much sense. And wasn't move/ownership semantics one of the tricky issues in considering #161? I'd worry that it would be too easy to lose track of true ownership, particularly if you ever call into an assembly compiled in any other language (or a previous version of C#).@ufcpp commented on Sun Jan 24 2016
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.
@alrz commented on Sun Jan 24 2016
@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
using
) does need a lightweight version of ownership, otherwise if you pass the object to another method then you're screwed. #161 and #160 are aboutdestructible
types andmove
keyword, but wi th an ownership system it would work for any type and there would be no need fordestructible
ormove
keywords.@HaloFour commented on Sun Jan 24 2016
@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.
@alrz commented on Sun Jan 24 2016
@HaloFour This is more related to #160 and #161 and somehow covering #181 but probably out of scope of this proposal, so I just give it up. 😄
@MgSam commented on Sun Jan 24 2016
If the 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 implementIDisposable
. ImplementingIDisposable
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 requireIDisposable
, you lose this benefit.I think method scoped
using
statements would be just as effective while being much more idiomatic.@gafter commented on Sun Jan 24 2016
I see lots and lots of code like this in Roslyn:
I would hate to use IDisposable for this.
defer
would be perfect, as it keeps the "begin" and related "end" code together.@leppie commented on Sun Jan 24 2016
@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
@GeirGrusom commented on Sun Jan 24 2016
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
using
extension? Allowing a type to determine what happens when the scope fails, succeeds and finishes (which is what using allows) could be useful.edit: it occurs to me that this is a digression. Sorry about that.
@ghord commented on Mon Jan 25 2016
There is great talk about implementing this feature in C++ by Andrei Alexandrescu. Some string arguemnts in favor of this there.
@alrz commented on Mon Jan 25 2016
@gafter That use case is really similar to what
Block
function does in Mathematica.Then there is no need for temporary variable and explicit
defer
to assign it back. If anything, I would prefer this (perhaps with another keyword) overdefer
because it really encourages bad design (for resource cleanup).As an alternative I'd suggest scoped assignments,
But it seems that this is useful only for this specific use case.
@MgSam commented on Mon Jan 25 2016
@gafter But that only works if there is no other logic you want to come after the restoration of
this.currentMethodOrLambda
. If there is, then you run the risk of the developer adding that into thedefer
block too, making your method into a confusing mess where the code that runs at the end is written at the top of the method body rather than the bottom. The last thing you want when you're reading code is to have to remember that there might be extra logic that takes place at the end of the method that was written out somewhere else entirely.@HaloFour commented on Mon Jan 25 2016
@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
try
/finally
which, while more verbose, is significantly more structured and easier to follow.@paulomorgado commented on Mon Jan 25 2016
I'm sorry @gafter, but I'm still failing to see the value of this proposal.
@DiryBoy commented on Mon Jan 25 2016
What is the order of multiple defer blocks, and what if exceptions throw in the middle?
@HaloFour commented on Tue Jan 26 2016
@DiryBoy If it were to be implemented as it is in Swift, the following C# code:
would produce the following output:
As far as I can tell it's illegal to throw from within the
defer
block.@alrz commented on Mon Jan 25 2016
It's like a
finally
block in the middle of nowhere so why not reusingfinally
instead ofdefer
. But when I think how messy it can get I tremble.@DiryBoy commented on Mon Jan 25 2016
@HaloFour Any method call within defer block could throw. Any code could throw before defer blocks get a chance to execute. The example code is a good example of writing code backwards without any good reason.
To address the org -> current -> org property change, if to introduce language support, IMO it's better to introduce some feature that could express that one off. For example
Edit: OK, duplicated with @alrz
@HaloFour commented on Mon Jan 25 2016
@DiryBoy
Swift is a lot stricter about exceptions than C# and doesn't permit calling functions that declare that they throw exceptions from within a
defer
block. As far as I can tell if a function were to fail unexpectedly, such as attempting to forcibly deference anil
, that's a fatal situation and the entire program stops. If the method throws outside of thedefer
blocks then those blocks are executed as expected. Not unlike wrapping the entire method body in a bunch oftry
/finally
clauses, exception for being in reverse order. I'm certainly not an expert in Swift, though.I'm not convinced that a temporary swap is so common as to require its own keyword and semantics. It's pretty easy to accomplish now with
try
/finally
or one of the helpers from Rx. I'd even argue that such logic depending on changing some shared state to temporarily change how the program behaves is itself a code smell and probably shouldn't be encouraged.@DiryBoy commented on Mon Jan 25 2016
@HaloFour
Thanks for sharing.
Me neither.
@alrz commented on Tue Jan 26 2016
@HaloFour
Exactly. They probably chose this pattern to avoid repeating a parameter in every function call and ended up with this.
@HaloFour commented on Tue Jan 26 2016
@paulomorgado Ah yeah, copypasta fail. I'll update.
@migueldeicaza commented on Thu Jan 28 2016
I love the idea of the
defer
statement in C#.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.
@GeirGrusom commented on Thu Jan 28 2016
I think
defer
is a very vague name though.@lachbaer commented on Thu Mar 24 2016
I wouldn't recommend 'mixing' the execution order of the code with
defer
for the same reason thatreturn
statements should not be present in the middle of a any method or elsewhere where they could easily be overssen.For the use-case @gafter mentioned I think it is besser to leave the assignment
this.currentMethodOrLambda = oldMethodOrLambda;
directly before the exit points in the method. This reminds the reader at the exit point that something is reverted to its original state.@temporaryfile commented on Fri Apr 15 2016
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.
@svick commented on Sat Apr 16 2016
@playsomethingsaxman
No, it won't. When you you use
using
on astruct
, theDispose()
method is called usingconstrained. callvirt
, which does not box the value.@temporaryfile commented on Sat Apr 16 2016
Niiice, learn something new every day. Not sure what we really need then.
@gafter commented on Sat Apr 16 2016
@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?
@temporaryfile commented on Sun Apr 17 2016
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
AggregateException
?@gafter commented on Sun Apr 17 2016
Yes, the alternative is pretty bad. Yet another reason to support the original proposal.
@whoisj commented on Sun Apr 17 2016
The
defer
statement is fairly novel. It's basically encouraging developers to perform RAII like operations (d'tors, etc) in immediately following code blocks instead of using scope characters (usually{
). However, it also keeps state as to what bits of code get executed based on which defer statements are actually reached at run time. No more of theC
clean up ofif (foo) free(foo); if (bar) free(bar);
or the equivalent of whatever Swift offers.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
defer
statement "throws"?@whoisj commented on Mon Apr 18 2016
Another observation/question: does it make more sense for the sequence of
defer
statements to be stack or queue oriented? For example, with the following snip-it of code what should the expected outcome be?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
finally
already has meaning and overloading the statement could cause all kinds of real errors to escape the compiler checks.@HaloFour commented on Mon Apr 18 2016
@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
defer!
macro) and C++ (via RAII, destructors are always called in reverse order).@svick commented on Mon Apr 18 2016
@whoisj Just like with
using
and RAII, I think that stack is the only real option. Consider that the non-deferred statements could have dependencies on each other. For example, this code that usesusing
:The natural way to rewrite it with
defer
would be:This would only work correctly if
defer
s were executed in reverse order.@whoisj commented on Mon Apr 18 2016
@svick good point! Explains the decision Swift made.
The
using
statement is fine, but it does requireIDisposable
which is less than optimal.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.
@aL3891 commented on Mon Apr 18 2016
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
Foo
be called 10 times with10
? or10
,9
,8
..? i'd imagine the first, unless a local variable is declared, same as if aFunc
was used?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 :)
@alrz commented on Tue Apr 19 2016
We might be able to do this with #6671 and code generators, marking the node we want to defer with a attribute and regenerate the syntax tree,
However, I'm not aware if generator API allows to
ReplaceNode
or that kind of stuff.@gafter commented on Tue Apr 19 2016
@aL3891
Since
defer
defers until the}
, and the}
immediately follows, that has the same behavior asDefer would not be an embedded-statement, so you could not use it as the controlled statement of an
if
. So no, there will be nodefer
s hidden in branches.@HaloFour commented on Tue Apr 19 2016
@gafter
I think more to the point, what would the behavior of the following be?
@gafter commented on Tue Apr 19 2016
@HaloFour prints
2
, just the same as if you wrote(Unless you get an exception between the two assignments)
@aL3891 commented on Wed Apr 20 2016
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.
@lachbaer commented on Tue May 03 2016
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 theuse
keyword:or shorter:
So, the requirement would be that every
defer
must follow a directly precedinguse
block.PS: you could use the more verbose
do
keyword instead ofuse
, a lookahead behind thedo
-block would tell if it has a loop or a defer meaning.PPS: adding the
defer
statement to thetry
statement additionally would then also be of use. Withtry
exceptions can also caught bycatch
blocks, but in contrast tofinally
those blocks are always excecuted when leaving the function.(I know that the sample doesn't make much sense and there are other, better ways, it's just a coding sample ;-)
@migueldeicaza commented on Thu May 26 2016
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 aDispose()
.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:
https://github.com/search?l=&o=desc&q=defer+language%3ASwift&ref=advsearch&s=indexed&type=Code&utf8=%E2%9C%93
@HaloFour commented on Thu May 26 2016
@migueldeicaza
Quickly scanning those examples I can easily see that the vast majority of cases are Swift's reimplementations of
using
orlock
. Surprisingly, much more the latter than the former. Of the remaining cases I mostly see bizarre ways of injecting mutation logic postreturn
, e.g.:I frankly don't see how that's more readable than the equivalent:
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 fortry
/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.@bbarry commented on Thu May 26 2016
@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: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?Or
yield
orawait
?@alrz commented on Thu May 26 2016
This prints
a 0 b 1
because in Swift each case body has its own block.No idea how it should work in C# though.
Beta Was this translation helpful? Give feedback.
All reactions