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

Using patterns and declarations #1703

Merged
merged 5 commits into from
Dec 20, 2018
Merged

Using patterns and declarations #1703

merged 5 commits into from
Dec 20, 2018

Conversation

jaredpar
Copy link
Member

@jaredpar jaredpar commented Jul 9, 2018

Proposal to add using local variable declarations and the using pattern to the language.

@jaredpar
Copy link
Member Author

jaredpar commented Jul 9, 2018

CC @agocke, @fayrose

@benaadams
Copy link
Member

Types which fit the disposable pattern can participate in a using statement or declaration without being required to implement IDisposable

So please this! 😄

Copy link
Contributor

@jnm2 jnm2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaredpar I don't know why I started doing this. Is this actually helpful, or would it be nice if I stopped? 😄

## Summary

The language will add two need capabilities around the `using` statement in order to make resoure
managemente simpler: recognize a `using` pattern in addition to `IDisposable` and add a `using`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typos resouce managemente

guidelines explicitly have an exception around braces for this scenario.

The `using` declaration removes much of the ceremony here and gets C# on par with other languages
that include resource management blocks. Additionally the `using` pattern lets a developers expand
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a developers


The `using` declaration removes much of the ceremony here and gets C# on par with other languages
that include resource management blocks. Additionally the `using` pattern lets a developers expand
the set of tyse that can participate here. In many cases removing the need to create wrapper types
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tyse

}
```

There are no restrictions around `goto`, are any other control flow construct in the face of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

areor

```

There are no restrictions around `goto`, are any other control flow construct in the face of
`using` declaration. Instead the code acts just as it would for the equivalent `using` statement:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a `using` declaration

```

A local declared in a `using` local declaration will be implicitly read-only. This matches the
behavior of locals declared in a `using` statement.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now this is cool.


In the situation where a type can be implicitly converted to `IDisposable` and also fits the
`using` pattern, then `IDisposable` will be prefered. While this takes the opposite of approach
of `foreach` (pattern prefered over interface) it is necessary for backwards compatibility.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo prefered twice, opposite of approach


The same restrictions from a traditional `using` statement apply here as well: local variables
declared in the `using` are read-only, a `null` value will not cause an exception to be thrown,
etc ... The code geneartion will be different only in that there will not be a cast to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

geneartion


### case labels without blocks

A `using declaration` is illegal directly inside a `case` label due to complications around it's
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it'sits

@jaredpar
Copy link
Member Author

jaredpar commented Jul 9, 2018

@jnm2

I don't know why I started doing this. Is this actually helpful, or would it be nice if I stopped?

Clearly based on your feedback, and the feedback of all my peers, I can use all the spelling help I can get. Especially when I'm jet lagged apparently. 😄

@OlegKarasik
Copy link

@jaredpar

Sorry if my question is dumb but is the purpose of auto-recognition of Disposable pattern is to avoid implicit cast to IDisposable where possible?

Because if you have already implemented Disposable method what is the difficulty to implement IDisposable.

@benaadams
Copy link
Member

Because if you have already implemented Disposable method what is the difficulty to implement IDisposable.

You can't implement an interface on a ref struct; so currently cannot take advantage of the using pattern with a Dispose method.

@jnm2
Copy link
Contributor

jnm2 commented Jul 9, 2018

@OlegKarasik Also: extension methods can make types disposable which you don't own.

@OlegKarasik
Copy link

@benaadams , @jnm2
OK, I got it. So basically the idea is the same as for 'foreach' and GetEnumerator() thing.

Thanks for the clarification.

@jnm2
Copy link
Contributor

jnm2 commented Jul 9, 2018

@OlegKarasik And some others, too: #1467


## Summary

The language will add two need capabilities around the `using` statement in order to make resource
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: need -> needed

Copy link

@Neme12 Neme12 Jul 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why couple these two unrelated proposals together?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is about improving the usability of the using statement hence grouping all aspects of that together.

@migueldeicaza
Copy link

migueldeicaza commented Jul 9, 2018

Would love to have a using that acts as a defer, to avoid scenarios where I need to either use an IDisposable, or add a "Dispose" method to the object being used. It could be an optional block after the expression:

using x = Marshal.AllocHGlobal (size) { Marshal.FreeHGlobal (x); }
    var n = Syscall.read (fd, x, size);
}

@Neme12
Copy link

Neme12 commented Jul 9, 2018

Is there an issue for this where we can discuss?

@jnm2
Copy link
Contributor

jnm2 commented Jul 9, 2018

@Neme12 Yes, you might want to move that to #1174 and read the discussion on the original proposal, #114.

@Neme12
Copy link

Neme12 commented Jul 9, 2018

Thanks. Moved my comment there

@@ -0,0 +1,164 @@
# using patterns and locals
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "using patterns" is confusing terminology. I think it should be "pattern-based using" instead (just like we do "pattern-base foreach", etc).

@jcouv
Copy link
Member

jcouv commented Jul 13, 2018

@jaredpar I made some minor edits for terminology ("pattern-based using", "disposable pattern") in d2ce4cc. I hope you don't mind.
Is the proposal ready to commit, since it was reviewed by LDM?

@jcouv
Copy link
Member

jcouv commented Jul 17, 2018

@jaredpar Is this ready to merge?

@jaredpar
Copy link
Member Author

@jcouv not yet. I need to update for the LDM meeting feedback.

@Hermholtz
Copy link

Hermholtz commented Oct 2, 2018

I hope it's a good place to discuss using declarations proposed for a future version of C#.

Here's the example I found:

{ 
    using var f1 = new FileStream("...");
    using var f2 = new FileStream("..."), f3 = new FileStream("...");
    ...
    // Dispose f3
    // Dispose f2 
    // Dispose f1
}

What I understood is that the compiler will try to call IDisposable.Dispose(), then - if absent - it will discover the void Dispose() methods and call them upon exiting of the scope. Perfect.

My question would be: why we need the using keyword in this scenario? Wouldn't the below work equally well? Couldn't the compiler do this magic for all declarations in the scope?

{ 
    var f1 = new FileStream("...");
    var f2 = new FileStream("..."), f3 = new FileStream("...");
    ...
    // Dispose f3
    // Dispose f2 
    // Dispose f1
}

@OlegKarasik
Copy link

@chojrak11

This sounds like RAII, C++ and automatic storage :)

The problem I see here is how it should work in that situation:

void A(FileStream fs)
{
  if (isReadyToDispose)
    fs.Dispose()
  else 
  {
    Task.Delay(1000).ContinueWith(t => { A(fs); });
  }
}

void B()
{
  var fs = new FileStream("...");
  A(fs);
}

Should the fs be disposed automatically when B method completes? If Yes then it will break the inner logic because A is really responsible for fs disposal.

I am not sure how this could be handled without usage of explicit using modifier.

@Hermholtz
Copy link

@OlegKarasik

This sounds like RAII, C++ and automatic storage :)

Yes, it does! Or at least 'deterministic destruction.' Dispose in this scenario is just a strange name for C++-like destructor, but well...

How would an explicit using help with your example?

@OlegKarasik
Copy link

@chojrak11

The explicit using defines whether compile should generate call to Dispose() or not.

Extending the previous example we could have the following:

void B()
{
  var fs = new FileStream("...");
  A(fs);

  using fs1 = new FileStream("...");

  // Dispose fs1
}

With explicit using it is obvious that fs shouldn't be disposed while fs1 should be.

@HaloFour
Copy link
Contributor

HaloFour commented Oct 2, 2018

@chojrak11

What you are suggesting would change the meaning of existing programs in a breaking way.

@Hermholtz
Copy link

@HaloFour just realized that. And if the Dispose() method was called differently in the new scenario? E.g. Destroy()?

@svick
Copy link
Contributor

svick commented Oct 2, 2018

@chojrak11 How would that help? If you had a class with Destroy, how do you write code that doesn't call it when the variable goes out of scope?

@Hermholtz
Copy link

By not defining it, I think. It's like asking 'how not to call the destructor in C++ when an object goes out of scope?' Maybe we're just realizing that C# needs automatic deterministic destruction, and this issue we're discussing is a good start to have that? Maybe using declaration is just a crippled version of it.

The Destroy name is not a good one, though. It'd need to be something that is not used in existing code.

@HaloFour
Copy link
Contributor

HaloFour commented Oct 2, 2018

@chojrak11

Maybe we're just realizing that C# needs automatic deterministic destruction, and this issue we're discussing is a good start to have that?

That's not what this proposal is attempting to achieve. Disposal is intentionally opt-in because there are plenty of scenarios where the current method would not want to dispose of the resource. Ownership is not a concept that is defined in C#.

@svick
Copy link
Contributor

svick commented Oct 2, 2018

@chojrak11

It's like asking 'how not to call the destructor in C++ when an object goes out of scope?'

What you do in C++ is that you allocate it dynamically ("on the heap"), possibly using some smart pointer type so that you still get deterministic destruction, but one that's not tied to a single block scope. But there's no such option in C#.

@Hermholtz
Copy link

Makes sense. It was worth trying, though.
Anyway, I'm very happy this feature (using declarations) is planned. Can't wait to use it :)

@msedi
Copy link

msedi commented Oct 2, 2018

Maybe we're just realizing that C# needs automatic deterministic destruction, and this issue we're discussing is a good start to have that?

I agree with with @chojrak11. We have a lot of occasions where we definitely know that the object/memory can go away.

I personally find the C++ CLI approach a little more initutive where there is a finalizer and a destructor.
So having a new/delete something would nice.

Nevertheless, this requirement doesn't fit in this discussion, just as a side note.

@HaloFour
Copy link
Contributor

HaloFour commented Oct 2, 2018

@msedi

I personally find the C++ CLI approach a little more initutive where there is a finalizer and a destructor.
So having a new/delete something would nice.

The "destructor" is just syntax candy for implementing IDisposable and delete just calls Dispose. You don't have control over releasing the memory of the object.

What C++/CLI offers that is interesting is stack-like semantics for working with reference types. The instances are still allocated on the managed heap and garbage collected, but you get copy semantics (requiring a copy constructor and assignment operator) and RAII (through IDisposable).

@msedi
Copy link

msedi commented Oct 2, 2018

@HaloFour:

The "destructor" is just syntax candy for implementing IDisposable and delete just calls Dispose. You don't have control over releasing the memory of the object.

Yes, sure. Nevertheless IDiposable is just an interface that supports a some unified way to do some sort of cleanup. The only problem is that there is no control if the object is GC'ed. Although I dislike the whole stuff that was introduced in C++ CLI it seems to me a lot is currently moving from the C++ syntax to C# which I think is not too bad. Maybe also the internals are also a bit handled differently, but in the end the result matters.

So something like a deterministic cleanup would really help a lot in scenarios that require more memory than the system supports even with virtual memory.

Our systems for example need more memory than is available, but there is not currently a good control to do your own memory manager in C# when the requirements are like ours. The only way to have a control is to fall back into unmanaged mode. But I think that shouldn't be the approach to handle such things.

In the end the discussion is a bit wider, e.g. setnewhandler in C++, WeakReferences, etc. But I think it doesn't fit into this discussion.

@Hermholtz
Copy link

Hermholtz commented Oct 2, 2018

@msedi remember that destruction/disposal is not only about memory deallocation. Memory is my least concern. I'm more concerned with database connections, files, network resources and so forth (this is what IDisposable was invented to help with).

I like the idea to open a file and have it automatically closed at the end of the scope. Having a single using keyword that signifies the intent in the declaration is elegant and acceptable:

void MyFunc(void) {
    using var myFile = File.Open(@"\path\to\file");
    // ... use the file

    // now it's automatically closed
}

while not having the using keyword lets me have a helper function that returns an open file:

File MyOpenFile(string path) {
    var myFile = File.Open(path);   // no 'using' marker
    return myFile;
    // no automatic cleanup
}

void MyFunc(void) {
    using var myFile = MyOpenFile(@"\path\to\file");
    // ... use the file

    // now it's automatically closed
}

In this proposal I only miss Golang's defer stuff, that is, registration of delegates to be executed at the end of the scope, as in below pseudo-C# example - that could trivially be implemented in the language.

void MyFunc(void) {
    using var myFile = File.Open(@"\path\to\file");
    defer () => Console.WriteLine("Finished processing the file.");
    defer () => Console.WriteLine("Another delegate.");
    // ... use the file

    // "Another delegate."
    // "Finished processing the file."
    // now the file is automatically closed
}

@msedi
Copy link

msedi commented Oct 2, 2018

@chojrak11: I absolutely agree. I had the same problem with files. The question is if you really need language support for this or are you able to create your own constructs around this problem?

For almost any of my classes that needed something you request here, I have created my own Disposable struct that take the object as a reference and remove/close/free the object when the end of the using block is hit. In that sense I'm completely free to do whatever I like.

@Hermholtz
Copy link

As this proposal replaces end of using blocks with end of enclosing scope, I'm just trying to explore additional possibilities it may open.

The defer stuff would be useful and is quite easy to add to the language.

@jaredpar jaredpar merged commit 6308115 into dotnet:master Dec 20, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.