-
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: Implicitly scoped using statement (16.3, Core 3) #114
Comments
I think this has a potential to introduce a lot of unintended consequences and unexpected behavior. How is this any better than the "before" code, except I didn't need to type 2 extra sets of parens and 2 extra sets of curly braces?
Disposability is non-deterministic and there isn't anything the compiler can do to tell the runtime GC that things should be disposed of in a certain order. |
And extra indentation. When your code doesn't even start until half-way off of the screen it's a big waste of real-estate.
This has nothing to do with GC. It is strictly syntax candy for |
I'll copy my comments from dotnet/roslyn#16579: This is very easy to confuse visually (I intentionally indented it wrong): using var stream1 = new MemoryStream();
string v1 = new StreamReader(stream1).ReadToEnd();
using (var stream1 = new MemoryStream())
string v1 = new StreamReader(stream1).ReadToEnd(); Even worse, here the indentation is fine: using var stream1 = new MemoryStream(); // Oops, stream1 will live on
using (var deflate = new DeflateStream(stream1, CompressionMode.Decompress))
string v1 = new StreamReader(deflate).ReadToEnd();
using (var stream1 = new MemoryStream())
using (var deflate = new DeflateStream(stream1, CompressionMode.Decompress))
string v1 = new StreamReader(deflate).ReadToEnd(); On the humorous side, since you aren't requiring parentheses, this could be legal: private static ILolz System;
void Lolz()
{
using System.Text;
// ...
} |
I'd argue that if you have so many nested using statements (or other nested code blocks) where that's a problem then you need to refactor into simpler/smaller methods. That point aside, I still don't see this giving us a real benefit but introduces a lot of places which will lead to incorrect code. (See @jnm2's earlier comment. All of the issues mentioned are legitimate concerns, are harder to read, and can introduce even harder to find runtime bugs.
It does have to do with GC as the |
@scottdorman @HaloFour's proposal still has the |
@jnm2 Ok. I think I understand the meaning here now. It's deterministic in the sense that this code public void Foo() {
using var connection = new SqlConnection(connectionString));
connection.Open();
using var command = connection.CreateCommand());
command.CommandText = "SELECT FOO FROM BAR";
using var reader = command.ExecuteReader());
while (reader.Read()) {
ProcessRecord(reader);
}
} will (roughly) expand to this code: public void Foo() {
var connection = new SqlConnection(connectionString));
try {
connection.Open();
var command = connection.CreateCommand();
try {
command.CommandText = "SELECT FOO FROM BAR";
var reader = command.ExecuteReader();
try {
while (reader.Read()) {
ProcessRecord(reader);
}
}
finally {
((IDisposable)reader)?.Dispose();
}
}
finally {
((IDisposable)command)?.Dispose();
}
}
finally {
((IDisposable)connection)?.Dispose();
}
} Which means the order of the calls to |
@scottdorman I don't like this syntax sugar at all, but the GC is not affected by this proposal. If you manually type the braces for the using statements, they way we work today, the effect on the GC is identical. |
@jnm2 The motivation in the initial post says
Maybe it's just the way I read it, but it specificly defines an order in which the resources are expected to be disposed, which isn't possible. Other than that, you're right, this proposal theoretically doesn't change the compiler generated expansion of the I'm still not a fan of this syntax as I think it has a huge potential to introduce hard to find/debug runtime errors and decreases the code readability. |
But you do have that guaranteed order of resource release in both scenarios! The GC doesn't release resources like connections, commands and readers. Dispose does that, deterministically, right when you tell it to. All GC does is free managed memory when it can be proved that freeing the memory has no effect on the executing code. Dispose and GC honestly have almost nothing to do with each other. |
Not completely right. Finalizers are called by GC and most holders of unmanaged resources do implement finalizer. There are two drawbacks of finalizers (and it's why they're not used in most real-life scenarios and
|
Yes, this proposed syntax is intended to behave (and expand) exactly the same as As for excessive nesting, the example above is real world and fairly common. It's three extra levels of nesting just for the sake of defining those blocks. In some cases it is possible to avoid that nesting. You can declare multiple resources in the same I'll give @jnm2 the concern that the syntax can be confusing. Afterall, the difference is only in parenthesis. Of the proposals on the Roslyn repo this was the syntax that @gafter seemed to prefer, so that's why I re-proposed it here. I'd be willing to explore other syntax. |
They absolutely have everything to do with each other. The only reason
Only if it's written properly, which many are not.
GC deals with both managed and unmanaged resources and does so by calling |
@HaloFour Yes, I realize the code example you gave is very real-world and have written many such blocks myself but have never felt it burdensome. To be honest, if we're going to introduce syntax sugar around dispose, I'd much rather see syntax sugar introduced that makes it easier/trivial to write the pattern properly (the That is, in my opinion, completely trivial when compared with ensuring the pattern is correct. A great example of this is a strongly typed WCF client, which does not follow the pattern properly and cannot be used inside a This proposal reduces the amount of indenting and curly braces I have to write, but introduces a much greater risk for invalid code due to the syntax. Given the syntax between the two referenced proposals, this syntax is better, but I still disagree that this feature is valuable. |
So what? In most cases this is at best a performance issue because the object may be disposed later than it could be. It may sometimes be a correctness issue if you're trying to reuse a resource that you think you released earlier but in fact you did not: using Stream s1 = File.Open(...);
s1.Write...
using Stream s2 = File.Open(...); // tough luck, this may fail due to sharing violation
s2.Write... But such code is rare in my experience.
Sort of. It would be more correct to say that it was created because it is practically impossible to deal with I'm mostly "for" this proposal but that may be due my C++ background where this is basically the "default" syntax. That said, I think that the most important drawback of this proposal is that it introduces an alternative way to do things. Such alternatives have already been introduced in the language but usually the new way is better and becomes the default while the old way becomes irrelevant (e.g. anonymous methods vs. lambda expressions). |
I'll let you borrow my Surface Pro 2 for a while. It was my primary development machine for a couple of years and it served that purpose very well. Not everyone has really wide monitors. 😄
Well, unless this becomes an either/or proposition and it's impossible for the team to consider both separately, there's no harm in proposing it. I completely acknowledge that this proposal is very minor in the grand scheme of things. I didn't even originally propose it, either on CodePlex or on Github. I just liked it enough, and knew that it at least had a little nod from members on the LDM team, that I thought it deserved to be brought over here. |
Fair enough. Screen size constraints are a real thing. I have a 3 monitor arrangement on my main development system, but definitely feel the loss of productivity when I work on my single-screen 15" laptop.
Absolutely. We would all be doing ourselves (and the language team) a disservice if we didn't bring up a proposal and weren't able to have discussions about it. That's the whole point here. Just because somebody (me, in this instance) may disagree with a proposal, doesn't mean it shouldn't still be discussed. |
@scottdorman It sounds like you are confusing
While each of these resources needs to be freed at some point, the timing of the first is particularly important - until the OS handle is closed, a user will not be able to open Explorer and delete the file. To ensure resources like this are only kept open while they are used, the
The I cannot recommend the following highly enough: http://joeduffyblog.com/2005/04/08/dg-update-dispose-finalization-and-resource-management/ |
Reducing the level of nesting is a great thing when it comes to make code legible and the syntax is almost the same so why not? :) Reiterating on what @MadsTorgersen wrote:
So let's not change the subject here, I mean it's about adding an alternative syntax so derailing the discussion into GC and/or resource management is really not the place to do it. |
I lean in favor of this proposal, but perhaps not for the reasons mentioned previously. One example where I think this could be useful is in recording telemetry around actions invoked by a user. In these situations, the telemetry recording which wraps the "real" work of a method can often be implemented using a // BEFORE
public async Task UserOperationAsync()
{
using (Telemetry.Operation(nameof(RunOperationAsync)))
{
// work here
}
}
// AFTER
public async Task UserOperationAsync()
{
using Telemetry.Operation(nameof(RunOperationAsync));
// work here
}
🐉 Then the 📝 I would consider this proposal relevant/related to #85, but while the scenarios overlap I wouldn't say either fully encompasses the other. |
I don't know but how do you feel about the following?
|
My only concerns there would be that it implies that the behavior is detached from the declaration, which is a big departure from the current behavior where the variable is declared at that point and is readonly. Would that assignment be legal to an existing variable? Could the variable be reassigned? Those differences might not be blockers, though. The compiler could still ensure that the instance itself is disposed, but there might be a question as to which scope that instance would be attached to. That of the variable, or that of where the public void Foo() {
SqlConnection connection = null;
if (condition) {
connection = using new SqlConnection(connectionString);
// do stuff here
}
// do other stuff here
}
// equivalent to
public void Foo() {
SqlConnection connection = null;
if (condition) {
using (var $temp = new SqlConnection(connectionString)) {
connection = $temp;
// do stuff here
}
}
// do other stuff here
}
// or equivalent to
public void Foo() {
SqlConnection connection = null;
try {
if (condition) {
connection = new SqlConnection(connectionString);
// do stuff here
}
// do other stuff here
}
finally {
(connection as IDisposable)?.Dispose();
}
} Personally I'd prefer the former. It might be weird that the variable lifetime and scope outlives that of the resource, and attempting to use it afterwards could result in exceptions. The compiler could potentially warn about that. It's no different than assigning off a reference to that instance anyway, which you've always been able to do. Furthermore, should the compiler care if you assign that instance to fields or |
Excellent point. The reason I suggested it is because people raised the concern where it looks too similar to the current statement and it might be confusing but yeah I fully agree with your point. |
@sharwell Fair enough (some of those comments may not have been as clear as they could have been). Yes, with the use of @eyalsk Agreed. The discussion about GC is off-topic to this proposal. Sorry for the temporary derailment. @sharwell's point about this being more akin to a scoped unit of work type scenario is valid and I'd still much rather see something around that than a different syntax to simply reduce the amount of indenting and which can introduce correctness problems. |
@jnm2 Copied from my proposal too First, for using (var stream1 = new MemoryStream())
string v1 = new StreamReader(stream1).ReadToEnd(); There are error Second, for using (var stream1 = new MemoryStream())
string v1 = new StreamReader(stream1).ReadToEnd();
using (var stream1 = new MemoryStream())
using var deflate = new DeflateStream(stream1, CompressionMode.Decompress);
string v1 = new StreamReader(deflate).ReadToEnd(); It will become obvious if you let formatter do indent using (var stream1 = new MemoryStream())
string v1 = new StreamReader(stream1).ReadToEnd();
using (var stream1 = new MemoryStream())
using var deflate = new DeflateStream(stream1, CompressionMode.Decompress);
string v1 = new StreamReader(deflate).ReadToEnd(); And |
We could avoid reassignment by forcing |
If there are condition need to met I would use ternary operator public void Foo()
{
using var connection = condition ? new SqlConnection(connectionString) : null;
if (connection != null) {
// do stuff here
}
// do other stuff here
} |
@Thaina like I said before: the embedded statement error doesn't matter. Pretend I removed the word Like I said before: the formatter does not indent like you say. Because there are no braces, both using statements stay at the same level of indent. This is the style used everywhere I've ever seen. So my original point here still stands as well. |
@jnm2 And like I was response to you that if you don't assign variable in that line then it would not cause any problem. It will only be a problem when you try to use any variable using (var stream1 = new MemoryStream())
new StreamReader(stream1).ReadToEnd();
// vs
using var stream1 = new MemoryStream();
new StreamReader(stream1).ReadToEnd(); What would be difference? it only that you would have |
Add telemetry and a couple of resources which can't be all instantiated back to back, plus namespace, class and method indentation and you're already 20+ spaces over. If you don't think that's a lot then I'll let you borrow my 10" Surface Pro 2 which was my primary development machine for about 4 years.
It wouldn't have to if you guys allowed for Every language that has a |
I don't see why teh snark is necessary. It's not like people are going out of their way to do this. This is a language that has been around a long time and which always has work being done with it continuously. That means that not every issue can be addressed. Would you like it if your customers treated every rough edge in any product you made as being maliciously intentional? :-/
I don't see how that would help here. The Deferable type had an |
Wasn't there a proposal somewhere for zero-allocation delegates? |
More poking fun. Let me add some emoji in there to better convey my intent.
Wait, you mean that they sometimes don't? 😜
Maybe I'd probably be more amenable to the idea of |
Is that not "language support"? |
Of course it is. So is adding |
I would like to omit the public void Foo() {
using connection = new SqlConnection(connectionString));
connection.Open();
using command = connection.CreateCommand());
command.CommandText = "SELECT FOO FROM BAR";
using reader = command.ExecuteReader());
while (reader.Read()) {
ProcessRecord(reader);
}
} |
There is a good reason to require parentheses as @jnm2 noted: In EC# there is a pattern where you can write things that ordinarily require braces without braces, causing them to treat the entire rest of the block as their subject:
So naturally I recommend that if this proposal is implemented, it should be implemented simply by allowing the braces to be omitted. From my perspective as author of EC#, I find the ambiguity of this proposal horrifying, because EC# uses a single interpretation mode for statements at all nesting levels (that is, you can write expressions like |
Braces can already be omitted with using statements today :) And htat already has a very specific meaning in the language. :) |
The championed tracking issue for this feature is #1174 . |
Also throwing my vote in favor. |
@xen2 this is already available in the latest Visual Studio 2019 preview, so you can download and try it out today. I'd also suggest you find a better diff tool, one that doesn't get confused by whitespace changes. I'm a big fan of Beyond Compare, very much worth the price. |
Shouldn't this issue be closed, since the change landed i C# 8.0? |
We don't close issues until the specification update is merged into the language. We have a draft speclet in the proposals folder, but nothing in the language itself yet. |
See dotnet/roslyn#5881 and dotnet/roslyn#181
Motivation (TL;DR edition):
Allow
using
keyword to be used to declare a variable that will be disposed at the end of the current block. This avoids creating a new block with an additional level of indentation:Before:
After:
As an implementation detail, the variables would be disposed in the reverse order in which they are declared. So given the above code, the order in which the resources would be disposed is
reader
,command
and thenconnection
.The text was updated successfully, but these errors were encountered: