Skip to content
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

Closed
gafter opened this issue Jan 22, 2016 · 58 comments
Closed

"defer" statement #8115

gafter opened this issue Jan 22, 2016 · 58 comments

Comments

@gafter
Copy link
Member

gafter commented 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 like

    {
        SomeType thing = Whatever...;
        defer {
            thing.Free();
        }
        // some code code using thing
    }

The 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

    {
        SomeType thing = Whatever...;
        try
        {
            // some code code using thing
        }
        finally
        {
            thing.Free();
        }
    }
@HaloFour
Copy link

@gafter I'd have to see use cases that aren't already sufficiently met through try/finally or using. It doesn't even seem that Apple could provide a worthwhile example of why you'd use it.

@alrz
Copy link
Contributor

alrz commented Jan 22, 2016

ِDoesn't RAII (#181) address this?

{
  using Disposable thing = ...;

  // disposed
}

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,

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 let instead).

Related: dotnet/csharplang#6611, #161.

@HaloFour
Copy link

@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 to using 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.

@pawchen
Copy link
Contributor

pawchen commented Jan 23, 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
Copy link
Contributor

jamesqo commented Jan 24, 2016

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 defer statement is needed in C#.

@gafter
Copy link
Member Author

gafter commented Jan 24, 2016

The advantages of the defer statement over the alternatives are

  • 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.

@jamesqo
Copy link
Contributor

jamesqo commented 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.

var resource = AcquireResource();

defer resource.Dispose(); // no braces

@paulomorgado
Copy link

Still not sold on that!

@HaloFour
Copy link

@gafter

  1. If 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.
  2. Rx provides some fantastic helper methods which can convert any operation into a disposable. Why add language support to something that can be done easily through a library?
  3. A corollary to this might be to expand using to support convention-based disposing rather than strictly requiring IDisposable, such as supporting objects that declare an instance Close() method.
  4. Neither would [Proposal] Allow for inline creation and handling of IDisposable objects (RAAI) #181 or a variation thereof. My personal preferred syntax would be using var disposable = new SomeDisposableObject(); Same mechanism but the resource is attached to the defining scope, no additional indentation required.
  5. See point 1, this only matters if you're not using IDisposable, which you should be using anyway.

If the purpose of defer is to provide resource cleanup I'd rather find ways of improving upon using 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:

var resource = new MyNonDisposableResource() using resource.Close();

@alrz
Copy link
Contributor

alrz commented Jan 25, 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 implementat IDisposable inetrface and explicitly call methods like Close or Free which in presence of IDisposable seem like code smells.

@HaloFour
Copy link

@alrz Using let as you describe seems like it would be in conflict with #6400 which is proposing that let 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
Copy link
Contributor

ufcpp commented Jan 25, 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
Copy link
Contributor

alrz commented Jan 25, 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 dotnet/csharplang#6611 are about destructible types and move keyword, but wi th an ownership system it would work for any type and there would be no need for destructible or move keywords.

@HaloFour
Copy link

@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
Copy link
Contributor

alrz commented Jan 25, 2016

@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. 😄

@MgSam
Copy link

MgSam commented Jan 25, 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 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.

I think method scoped using statements would be just as effective while being much more idiomatic.

@gafter
Copy link
Member Author

gafter commented Jan 25, 2016

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. defer would be perfect, as it keeps the "begin" and related "end" code together.

            var oldMethodOrLambda = this.currentMethodOrLambda;
            this.currentMethodOrLambda = node.Symbol;
            defer this.currentMethodOrLambda = oldMethodOrLambda;
            ... // process the current lambda

@leppie
Copy link
Contributor

leppie commented Jan 25, 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
Copy link

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.

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.

@ghord
Copy link

ghord commented 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
Copy link
Contributor

alrz commented Jan 25, 2016

@gafter That use case is really similar to what Block function does in Mathematica.

block(this.currentMethodOrLambda = node.Symbol) 
{
}

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) over defer because it really encourages bad design (for resource cleanup).

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.

@MgSam
Copy link

MgSam commented 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 the defer 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
Copy link

@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.

var oldMethodOrLambda = this.currentMethodOrLambda;
try
{
    this.currentMethodOrLambda = node.Symbol;
    // ... process the current lambda
}
finally {
    this.currentMethodOrLambda = oldMethodOrLambda;
}

@paulomorgado
Copy link

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. defer would be perfect, as it keeps the "begin" and related "end" code together.

        var oldMethodOrLambda = this.currentMethodOrLambda;
        this.currentMethodOrLambda = node.Symbol;
        defer this.currentMethodOrLambda = oldMethodOrLambda;
        ... // process the current lambda

I'm sorry @gafter, but I'm still failing to see the value of this proposal.

@pawchen
Copy link
Contributor

pawchen commented Jan 26, 2016

What is the order of multiple defer blocks, and what if exceptions throw in the middle?

@HaloFour
Copy link

@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:

Hello
World
Baz
Bar
Foo

As far as I can tell it's illegal to throw from within the defer block.

@alrz
Copy link
Contributor

alrz commented Jan 26, 2016

It's like a finally block in the middle of nowhere so why not reusing finally instead of defer. But when I think how messy it can get I tremble.

@migueldeicaza
Copy link

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
Copy link

I think defer is a very vague name though.

@lachbaer
Copy link
Contributor

I wouldn't recommend 'mixing' the execution order of the code with defer for the same reason that return 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
Copy link

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
Copy link
Contributor

svick commented Apr 16, 2016

@playsomethingsaxman

The problem with a struct implementing IDisposable to solve this pattern everywhere is that Dispose() will cause it to be boxed.

No, it won't. When you you use using on a struct, the Dispose() method is called using constrained. callvirt, which does not box the value.

@temporaryfile
Copy link

No, it won't. When you you use using on a struct, the Dispose() method is called using constrained. callvirt, which does not box the value.

Niiice, learn something new every day. Not sure what we really need then.

@gafter
Copy link
Member Author

gafter commented Apr 16, 2016

@playsomethingsaxman

Not sure what we really need then.

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
Copy link

temporaryfile commented Apr 17, 2016

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...

using (var deferrals = new DeferralStack())
{
    SomeType thing = Whatever;
    deferrals.Defer(() => thing.Free());

    SomeType thing2 = Whatever;
    deferrals.Defer(() => thing2.Free());
}

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
Copy link
Member Author

gafter commented Apr 18, 2016

To me that would be a nightmare to step through and debug

Yes, the alternative is pretty bad. Yet another reason to support the original proposal.

@whoisj
Copy link

whoisj commented Apr 18, 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 the C clean up of if (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
Copy link

whoisj commented 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?

{
    Console.WriteLine("A");
    defer { Console.WriteLine("One"); }
    Console.WriteLine("B");
    defer { Console.WriteLine("Two"); }
    Console.WriteLine("C");
    defer { Console.WriteLine("Three"); }
}

Should it be:

A
B
C
Three
Two
One

Or

A
B
C
One
Two
Three

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?

It's like a finally block in the middle of nowhere so why not reusing finally instead of defer. But when I think how messy it can get I tremble.

I agree, but finally already has meaning and overloading the statement could cause all kinds of real errors to escape the compiler checks.

@HaloFour
Copy link

@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
Copy link
Contributor

svick commented 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 uses using:

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 defer would be:

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 defers were executed in reverse order.

@whoisj
Copy link

whoisj commented Apr 18, 2016

@svick good point! Explains the decision Swift made.

The using statement is fine, but it does require IDisposable 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
Copy link

aL3891 commented 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

for(var i = 0; i<10; i++) {
    defer Something(i);
}

Would Foobe called 10 times with 10? or 10,9,8..? i'd imagine the first, unless a local variable is declared, same as if a Func was used?

If defer statements can contain anything, they can also change state i assume, so this would be legal:

var thing = GetThing();
if(Test())
    defer thing.baz = 30
defer thing = GetOtherThing();
defer thing = null;

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
Copy link
Contributor

alrz commented Apr 19, 2016

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 ReplaceNode or that kind of stuff.

@gafter
Copy link
Member Author

gafter commented Apr 19, 2016

@aL3891

Also, what is the closure situation? Consider

for(var i = 0; i<10; i++) {
    defer Something(i);
}

Since defer defers until the }, and the } immediately follows, that has the same behavior as

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 if. So no, there will be no defers hidden in branches.

@HaloFour
Copy link

@gafter

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;
}

@gafter
Copy link
Member Author

gafter commented Apr 19, 2016

@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)

@aL3891
Copy link

aL3891 commented 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
Copy link
Contributor

lachbaer commented May 3, 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 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 ;-)

@migueldeicaza
Copy link

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:

https://github.com/search?l=&o=desc&q=defer+language%3ASwift&ref=advsearch&s=indexed&type=Code&utf8=%E2%9C%93

@HaloFour
Copy link

@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.

@bbarry
Copy link

bbarry commented 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:

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?

@alrz
Copy link
Contributor

alrz commented May 26, 2016

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.

@gafter
Copy link
Member Author

gafter commented Apr 28, 2017

Issue moved to dotnet/csharplang dotnet/roslyn#513 via ZenHub

@gafter gafter closed this as completed Apr 28, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests