-
Notifications
You must be signed in to change notification settings - Fork 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
Champion "defer statement" #1398
Comments
I guess this has been brought up before but just to mention, #1174 + "convention-based Dispose" could address the use cases for a "defer statement" (edit: except that it's not anything like resource cleanup, which I believe, such language construction would encourage). |
I feel like, outside of calling I also think it's very easy to misuse. Not always the case, but generally if you have more than one cleanup block in your code it's a code smell. #1174 seems much easier for users to use correctly from the standpoint of separation of concerns. |
One valid use case that I can think of, is to save the current state in traversal of some tree structure and reverting back that state once all children are visited. Still, since this feature is really easy to abuse, I think as long as we keep it undocumented we're fine. |
In the spirit of designing the language for myself, there are hundreds of places in the compiler where this would be a simplification of existing code. |
@gafter wouldn't "convention-based Dispose" plus a struct similarly not create an object? |
@yaakov-h Sure you can do that. And you're going to store a delegate in the struct I presume so you can pass a lambda with your arbitrary code in it? |
Static delegates cannot capture anything |
@gafter, static delegates by themselves don't capture anything (as proposed). This does not, however, mean the compiler could not create some struct which allowed state to be carried and passed to a static delegate when invoked (such as to support some other feature). |
(a user could of course do the same with static delegates) |
@gafter {
SomeType thing = Whatever...;
defer {
thing.Free();
}
if (SomeCondition) goto outside; //would "thing" be freed before goto outside?
//some code
}
outside:
//some code |
@ygc369 Doesn't the same question exist with |
It's still unclear how it would be implemented? |
I wonder if it's more common need in the compiler than in other code. And since compiler writers are not really a target demographic of C#, I think their needs are not that important. "Designing for myself" can work, but I think you need to make sure you're not designing just for yourself.
Sure, but the So I think you could do something like: using scoped Defer(thing, static t => t.Free());
using scoped Defer(ref thing, static (ref SomeType t) => t.Free()); |
I'd love to hear more about these use cases you envision for I'm not terribly concerned about how the @gafter's of the world might use this feature. It's everyone else. Also, assuming that |
@HaloFour I assume you're not asking me. |
Yes, the question is in response to your statement which I should've quoted:
I'm not trying to challenge your statement, I just want to understand the value-add where existing syntax doesn't suffice for one reason or another. |
I am support your whole idea and mechanism. But I still thinking that we should not need Because mechanism-wise, this is actually the As I was propose #249 that we should able to create anonymous local function. In this case we should be able to create local function for mocking the As for the simplest case // Just stranspile in the same way as defer
var thing = Whatever...;
using ~> { // syntax for defer
thing.Free();
} And for inline case // Just stranspile in the same way as defer
using var thing = Whatever... ~> { // syntax for defer
thing.Free();
}; Also I was once propose dotnet/roslyn#16958 about return using. It would be good if this syntax would be generalized to be disposable object that could be returned |
Just going with @Thaina's syntax, using var thing = new ThingObj(arg, anotherArg) ~> thing.Free(); could be a possible one-liner. Although in this case, |
The point is to avoid creating unnecessary garbage. Not even a delegate. This should be as efficient as other local control-flow. |
If we have implicitly-scoped |
@gafter That's why I try and try to propose #249 that we should always have a way to create anonymous local function that was not create And as I said it should be compiler optimization to just know that If there are We don't need |
@Thaina As long as the language-defined semantics do not require the creation of a delegate or the lifting of variables into closure classes, we don't need any "optimizations" to implement |
@gafter I actually have a little difference opinion about this. I was actually think about I was thinking of Generally we could just always make inline optimization for It would be that from the start we might allow |
What about filtering of exception in a defer block ? var transaction = new SomeTransaction();
using(transaction)
{
defer (Exception? ex) => if (ex != null) { transaction.Commit(); }
// ...
// Use transaction
// ...
throw new InvalidOperationException("Some reason");
} |
I like this proposal but I feel we might be able to reuse
The only case that I see where this would overlap with existing syntax (and to be clear I'm proposing that try-catch-finally should remain unchanged and work exactly like it does today) is if you wanted to express this:
Not sure if this has much value ( |
All the nope. In simple code snippets this works. But in real world code you would see
and have no idea what is actually happening without checking whether or not that I already dislike the proposal because it makes code harder to understand, but reusing |
I really don't understand why we want a backwards finally block. But if you really do need it, here it is:
The only reason we don't do this now is that it's much harder to read than finally blocks. But if you really want it, you have it now. Use it for a few years to prove a special keyword is justified. |
@Grauenwolf It's already been discussed why using a lambda/wrapper isn't acceptable. For the performance-sensitive scenarios even pattern-based dispose wouldn't be enough. |
@Grauenwolf Is it really any worse than the invisible call to
|
This conversation sounds similar to the case of finally in C++: on one hand you have the "if you need finally, your class is not designed well, it should be RAII" people, and then there's the workaround of making a dummy object with a destructor to achieve defer/finally-like behavior. |
Performance? Do you have any metrics that prove this would really be an issue? This is already a pretty obscure feature that is better handled by That doesn't seem plausible to me. So I'm going to reiterate my challenge. Show us some read-world code employing |
@Grauenwolf I'm happy to work on a benchmark to at least demonstrate what the difference would be, but I don't think performance (by itself) is necessary to justify the feature. Delegates open the door to captures or other mistakes. Why is Regarding performance, it's just a straight fact that delegates would be slower with pointless allocations. Who cares whether it's a make-or-break difference for any given application? Why not expose faster paths for those who want it? |
They do have the same problems, hence my challenge. If you can prove that Either way it will settle the debate with code rather than words, which I think we can all agree is preferable. |
In my opinion, it's more about the fact that I'm also concerned that a carte blanche feature like Lastly, I have a lot of questions as to when and how |
@HaloFour That is largely the point. I want to The Go form of |
Shame |
That's kinda the point. If the scenario is resource disposal then you should just use |
@gafter defer struct FileManager
{
FileStream? file;
public FileManager(string filename)
{
file = new FileStream(filename, FileMode.Open);
}
defer // the defer block in a defer struct is automatically executed once the struct instance goes out of scope.
{
if(file)
{
file.Close();
file=null;
}
}
} We should introduce the concept of defer struct, it is a special kind of struct, only used for wrapping resources, it should have following features: Why to introduce defer struct? |
That's basically RAII lite. Other than it being done automatically (which to my knowledge is deliberately not done in C# compared to, e.g., C++/CLI, happy to be corrected), what benefits does this bring over |
The deferable struct sounds interesting, but it would only make sense if we had a matching resource that could only be stack-allocated. By the same token, if we had a resource that could only be stack-allocated, we couldn't safely use IDisposable and would need a deferable struct. But the elephant in the room is whether or not such a resource exists. |
@Grauenwolf I thought C# already supported that scenario? |
In my imagination, this deferable struct would be recognized by the compiler so it wouldn't need an explicit |
It’s essentially asking for destructors in a struct. If the CLR adds those, fine, use them for RAII if you like. |
Yes.
Add a limitation: deferable struct can't be declared as
The deferable struct itself must be stack-allocated, but the resource it wraps don't need to be stack-allocated. In my example, the resource |
i know C# has its selfs ways,but its long syntax. |
See #513
This is to be considered alongside #1174 (though we could elect to do one without the other)
The text was updated successfully, but these errors were encountered: