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" #1174

Open
gafter opened this Issue Dec 6, 2017 · 97 comments

Comments

Projects
None yet
@gafter
Member

gafter commented Dec 6, 2017

  • Proposal added (#1703)
  • Discussed in LDM
  • Decision in LDM
  • Finalized (done, rejected, inactive)
  • Spec'ed

This is for some form of using statement, where the statements executed while the resource is held are the following statements in the same block that contains the using statement; the resource is freed at the end of the block.

See #114 and #114 (comment)

LDM history:

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Dec 7, 2017

Downvote retracted. It was based on a misunderstanding on my part, over both how the current syntax works and a misreading of the new syntax.

Downvoting this. I didn't see the need for it when @HaloFour first proposed it, and I don't see a need now.

This is a "saving a few characters and a bit of indentation" proposal that makes the code harder to read as the usings have no obvious scope and just "magically" disappear at the end of the method. And since we now have local functions, there's even less need for it. Taken the original code example, it can already be expressed as:

public void Foo()
{
    using (var connection = OpenConnection(new SqlConnection(connectionString)))
    using (var command = SetupCommand(connection.CreateCommand()))
    using (var reader = command.ExecuteReader())
    {
        while (reader.Read())
        {
            ProcessRecord(reader);
        }
    }

    SqlConnection OpenConnection(SqlConnection connection)
    {
        connection.Open();
        return connection;
    }

    SqlCommand SetupCommand(SqlCommand command)
    {
        command.CommandText = "SELECT FOO FROM BAR";
        return command;
    }
}

In my view, in the above code it is immediately apparent as to the scope of the using blocks, which the proposal in #114 lacks.

DavidArno commented Dec 7, 2017

Downvote retracted. It was based on a misunderstanding on my part, over both how the current syntax works and a misreading of the new syntax.

Downvoting this. I didn't see the need for it when @HaloFour first proposed it, and I don't see a need now.

This is a "saving a few characters and a bit of indentation" proposal that makes the code harder to read as the usings have no obvious scope and just "magically" disappear at the end of the method. And since we now have local functions, there's even less need for it. Taken the original code example, it can already be expressed as:

public void Foo()
{
    using (var connection = OpenConnection(new SqlConnection(connectionString)))
    using (var command = SetupCommand(connection.CreateCommand()))
    using (var reader = command.ExecuteReader())
    {
        while (reader.Read())
        {
            ProcessRecord(reader);
        }
    }

    SqlConnection OpenConnection(SqlConnection connection)
    {
        connection.Open();
        return connection;
    }

    SqlCommand SetupCommand(SqlCommand command)
    {
        command.CommandText = "SELECT FOO FROM BAR";
        return command;
    }
}

In my view, in the above code it is immediately apparent as to the scope of the using blocks, which the proposal in #114 lacks.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Dec 7, 2017

Contributor

@DavidArno

That instantly doubles the length of the method. Sure, it works, and sure, it avoids indentation, but at the cost of much unnecessary verbosity.

The scope of variables within a block is already a well defined concept in C#. There's no more "magick" here than should be obvious and expected.

Contributor

HaloFour commented Dec 7, 2017

@DavidArno

That instantly doubles the length of the method. Sure, it works, and sure, it avoids indentation, but at the cost of much unnecessary verbosity.

The scope of variables within a block is already a well defined concept in C#. There's no more "magick" here than should be obvious and expected.

@mikedn

This comment has been minimized.

Show comment
Hide comment
@mikedn

mikedn Dec 7, 2017

the usings have no obvious scope and just "magically" disappear at the end of the method

They do not disappear at the end of the method, they disappear at the end of the scope the variable was declared in. That's natural, there's nothing magic about it.

In fact it can be argued that the current using makes no sense because it forces you to create a new scope for no reason. Though it's sometimes useful when you need the using scope to be smaller than an already existing scope. When this happens in C++ what people do is create e block just for that:

void foo()
{
    {
        ObjX x;
       // use x...
    } // destroy x

    // some other code which does not need x
}

It's a bit weird but in my experience this need does arise too often.

mikedn commented Dec 7, 2017

the usings have no obvious scope and just "magically" disappear at the end of the method

They do not disappear at the end of the method, they disappear at the end of the scope the variable was declared in. That's natural, there's nothing magic about it.

In fact it can be argued that the current using makes no sense because it forces you to create a new scope for no reason. Though it's sometimes useful when you need the using scope to be smaller than an already existing scope. When this happens in C++ what people do is create e block just for that:

void foo()
{
    {
        ObjX x;
       // use x...
    } // destroy x

    // some other code which does not need x
}

It's a bit weird but in my experience this need does arise too often.

@gulshan

This comment has been minimized.

Show comment
Hide comment
@gulshan

gulshan Dec 7, 2017

Kotlin has apply and also functions in its stdlib, which perform an action on a object and returns it. With them the code can be like-

public void Foo()
{
    using (var connection = new SqlConnection(connectionString).Apply(c => { c.Open; }))
    using (var command = connection.CreateCommand()
                          .Apply(c => { c.CommandText = "SELECT FOO FROM BAR"; }))
    using (var reader = command.ExecuteReader())
    {
        while (reader.Read())
        {
            ProcessRecord(reader);
        }
    }
}

gulshan commented Dec 7, 2017

Kotlin has apply and also functions in its stdlib, which perform an action on a object and returns it. With them the code can be like-

public void Foo()
{
    using (var connection = new SqlConnection(connectionString).Apply(c => { c.Open; }))
    using (var command = connection.CreateCommand()
                          .Apply(c => { c.CommandText = "SELECT FOO FROM BAR"; }))
    using (var reader = command.ExecuteReader())
    {
        while (reader.Read())
        {
            ProcessRecord(reader);
        }
    }
}
@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Dec 7, 2017

@mikedn,

They do not disappear at the end of the method, they disappear at the end of the scope the variable was declared in.

Oh, that would be nasty. That suddenly brings all the issues around ; that C suffers from with while etc when a ; is accidentally placed after it:

using (var connection = OpenConnection(new SqlConnection(connectionString)))
using (var command = SetupCommand(connection.CreateCommand()));  // <- semicolon here
using (var reader = command.ExecuteReader())	// won't compile;command is now out of scope

@HaloFour,

... but at the cost of much unnecessary verbosity.

Clarity is never unnecessary verbosity. As you have pointed out to me on a number of occasions, it's never worth saving a few lines of code if the result is less clarity.

DavidArno commented Dec 7, 2017

@mikedn,

They do not disappear at the end of the method, they disappear at the end of the scope the variable was declared in.

Oh, that would be nasty. That suddenly brings all the issues around ; that C suffers from with while etc when a ; is accidentally placed after it:

using (var connection = OpenConnection(new SqlConnection(connectionString)))
using (var command = SetupCommand(connection.CreateCommand()));  // <- semicolon here
using (var reader = command.ExecuteReader())	// won't compile;command is now out of scope

@HaloFour,

... but at the cost of much unnecessary verbosity.

Clarity is never unnecessary verbosity. As you have pointed out to me on a number of occasions, it's never worth saving a few lines of code if the result is less clarity.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Dec 7, 2017

Contributor

@DavidArno

Oh, that would be nasty. That suddenly brings all the issues around ; that C suffers from with while etc when a ; is accidentally placed after it:

Finding a syntax which better differentiates between the two forms would be a part of the design process. I agree that the feature should be made resilient to those kinds of issues. The syntax I've suggested is illustrative only.

Clarity is never unnecessary verbosity. As you have pointed out to me on a number of occasions, it's never worth saving a few lines of code if the result is less clarity.

Clarity is very subjective. The C# language does not require defining scope for every declared variable and it's pretty clear that the scope is inherited from the current block. In many non-GC languages a deterministic destruction/disposal when a variable goes out of scope is very common.

Contributor

HaloFour commented Dec 7, 2017

@DavidArno

Oh, that would be nasty. That suddenly brings all the issues around ; that C suffers from with while etc when a ; is accidentally placed after it:

Finding a syntax which better differentiates between the two forms would be a part of the design process. I agree that the feature should be made resilient to those kinds of issues. The syntax I've suggested is illustrative only.

Clarity is never unnecessary verbosity. As you have pointed out to me on a number of occasions, it's never worth saving a few lines of code if the result is less clarity.

Clarity is very subjective. The C# language does not require defining scope for every declared variable and it's pretty clear that the scope is inherited from the current block. In many non-GC languages a deterministic destruction/disposal when a variable goes out of scope is very common.

@mikedn

This comment has been minimized.

Show comment
Hide comment
@mikedn

mikedn Dec 7, 2017

Oh, that would be nasty. That suddenly brings all the issues around ; that C suffers from with while etc when a ; is accidentally placed after it:

I do not understand your example and the C "issue" it is supposed to illustrate.

mikedn commented Dec 7, 2017

Oh, that would be nasty. That suddenly brings all the issues around ; that C suffers from with while etc when a ; is accidentally placed after it:

I do not understand your example and the C "issue" it is supposed to illustrate.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Dec 7, 2017

@mikedn,

using (var connection = OpenConnection(new SqlConnection(connectionString)))
using (var command = SetupCommand(connection.CreateCommand()))
using (var reader = command.ExecuteReader())
DoSomething(reader);

is equivalent to:

using (var connection = OpenConnection(new SqlConnection(connectionString)))
{
    using (var command = SetupCommand(connection.CreateCommand()))
    {
        using (var reader = command.ExecuteReader())
        {
            DoSomething(reader);
        }
    }
}

So if changed to:

using (var connection = OpenConnection(new SqlConnection(connectionString)))
using (var command = SetupCommand(connection.CreateCommand())); // <- add semicolon
using (var reader = command.ExecuteReader())
DoSomething(reader);

It becomes equivalent to:

using (var connection = OpenConnection(new SqlConnection(connectionString)))
{
    using (var command = SetupCommand(connection.CreateCommand())) {}
}
using (var reader = command.ExecuteReader()) // command is out of scope here
{
    DoSomething(reader);
}

Adding or removing a ; would completely change the implied block structure of the code, leading to at least hard to figure out compiler errors, if not runtime errors.

This same problem used to plague me when writing C. Accidentally put a semicolon in the wrong place and things would go wrong:

while (true); // <- semicolon here
{
    // this block is unrelated to the while and is unreachable
}

DavidArno commented Dec 7, 2017

@mikedn,

using (var connection = OpenConnection(new SqlConnection(connectionString)))
using (var command = SetupCommand(connection.CreateCommand()))
using (var reader = command.ExecuteReader())
DoSomething(reader);

is equivalent to:

using (var connection = OpenConnection(new SqlConnection(connectionString)))
{
    using (var command = SetupCommand(connection.CreateCommand()))
    {
        using (var reader = command.ExecuteReader())
        {
            DoSomething(reader);
        }
    }
}

So if changed to:

using (var connection = OpenConnection(new SqlConnection(connectionString)))
using (var command = SetupCommand(connection.CreateCommand())); // <- add semicolon
using (var reader = command.ExecuteReader())
DoSomething(reader);

It becomes equivalent to:

using (var connection = OpenConnection(new SqlConnection(connectionString)))
{
    using (var command = SetupCommand(connection.CreateCommand())) {}
}
using (var reader = command.ExecuteReader()) // command is out of scope here
{
    DoSomething(reader);
}

Adding or removing a ; would completely change the implied block structure of the code, leading to at least hard to figure out compiler errors, if not runtime errors.

This same problem used to plague me when writing C. Accidentally put a semicolon in the wrong place and things would go wrong:

while (true); // <- semicolon here
{
    // this block is unrelated to the while and is unreachable
}
@mikedn

This comment has been minimized.

Show comment
Hide comment
@mikedn

mikedn Dec 7, 2017

Hmm?!

using (var connection = OpenConnection(new SqlConnection(connectionString)))
using (var command = SetupCommand(connection.CreateCommand())); // <- add semicolon
using (var reader = command.ExecuteReader())
DoSomething(reader);

That's not how it's supposed to look like if this proposal is implemented. It's supposed to look like

using var connection = OpenConnection(new SqlConnection(connectionString));
using var command = SetupCommand(connection.CreateCommand());
using var reader = command.ExecuteReader();
DoSomething(reader);

The semicolon is required, like for every other variable declaration.

mikedn commented Dec 7, 2017

Hmm?!

using (var connection = OpenConnection(new SqlConnection(connectionString)))
using (var command = SetupCommand(connection.CreateCommand())); // <- add semicolon
using (var reader = command.ExecuteReader())
DoSomething(reader);

That's not how it's supposed to look like if this proposal is implemented. It's supposed to look like

using var connection = OpenConnection(new SqlConnection(connectionString));
using var command = SetupCommand(connection.CreateCommand());
using var reader = command.ExecuteReader();
DoSomething(reader);

The semicolon is required, like for every other variable declaration.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Dec 7, 2017

Contributor

@DavidArno

It sounds like you're arguing against the current syntax of using, for which I'm afraid you're a little too late.

This proposal wouldn't have the same issue because the scope would not be tied to an embedded statement immediately following the statement. However, there is valid concern that the syntax I've suggested is too similar to the existing syntax since the only difference is the lack of parenthesis and the requirement of a semicolon. That's why @TyOverby suggested a different syntax employing a new keyword, using scope.

Contributor

HaloFour commented Dec 7, 2017

@DavidArno

It sounds like you're arguing against the current syntax of using, for which I'm afraid you're a little too late.

This proposal wouldn't have the same issue because the scope would not be tied to an embedded statement immediately following the statement. However, there is valid concern that the syntax I've suggested is too similar to the existing syntax since the only difference is the lack of parenthesis and the requirement of a semicolon. That's why @TyOverby suggested a different syntax employing a new keyword, using scope.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Dec 7, 2017

@HaloFour,

I had mistakenly thought that using ((var x = new Disposable())) was valid and thus that () could still be wrapped around your new syntax. This isn't the case. So that removes my fear over random ; affecting things. 😊

using scope or some such would help to distinguish them better though.

DavidArno commented Dec 7, 2017

@HaloFour,

I had mistakenly thought that using ((var x = new Disposable())) was valid and thus that () could still be wrapped around your new syntax. This isn't the case. So that removes my fear over random ; affecting things. 😊

using scope or some such would help to distinguish them better though.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Dec 7, 2017

Contributor

sequence expressions + with clause?

using (var connection = new SqlConnection(connectionString))
using (var command = (connection.Open(); connection.CreateCommand() with { CommandText = sql }))
using (var reader = command.ExecuteReader())
{ .. }

oh no.

Contributor

alrz commented Dec 7, 2017

sequence expressions + with clause?

using (var connection = new SqlConnection(connectionString))
using (var command = (connection.Open(); connection.CreateCommand() with { CommandText = sql }))
using (var reader = command.ExecuteReader())
{ .. }

oh no.

@MkazemAkhgary

This comment has been minimized.

Show comment
Hide comment
@MkazemAkhgary

MkazemAkhgary Dec 7, 2017

could using be applied to assignment only?

SqlConnection connection;
using connection = new ...; // connection becomes readonly afterwards

if not, then declaration could be implied by one keyword so instead of using var you type using and that declares new variable. so using x = ... would actually declare read only x. I'm not proposing this though.

MkazemAkhgary commented Dec 7, 2017

could using be applied to assignment only?

SqlConnection connection;
using connection = new ...; // connection becomes readonly afterwards

if not, then declaration could be implied by one keyword so instead of using var you type using and that declares new variable. so using x = ... would actually declare read only x. I'm not proposing this though.

@ygc369

This comment has been minimized.

Show comment
Hide comment
@ygc369

ygc369 Dec 8, 2017

@HaloFour @gafter
Is this similar to #121?

ygc369 commented Dec 8, 2017

@HaloFour @gafter
Is this similar to #121?

@ghord

This comment has been minimized.

Show comment
Hide comment
@ghord

ghord Dec 8, 2017

Following code does not compile right now:

    goto asdf;
    using (File.Open("test", FileMode.Open))
    {
        asdf:;
    }

With CS0159: No such label 'asdf' within the scope of the goto statement.

I guess

    goto asdf;
    using File.Open("test");
    asdf:;

Should be disallowed too.

ghord commented Dec 8, 2017

Following code does not compile right now:

    goto asdf;
    using (File.Open("test", FileMode.Open))
    {
        asdf:;
    }

With CS0159: No such label 'asdf' within the scope of the goto statement.

I guess

    goto asdf;
    using File.Open("test");
    asdf:;

Should be disallowed too.

@mikedn

This comment has been minimized.

Show comment
Hide comment
@mikedn

mikedn Dec 8, 2017

using File.Open("test"); doesn't make sense in this context, it should be a variable declaration. So the question is what to do about:

goto asdf;
using var file = File.Open("test");
asdf:;

If code after asdf is trying to use file then you'd get a Use of unassigned variable error so it's all good probably.

mikedn commented Dec 8, 2017

using File.Open("test"); doesn't make sense in this context, it should be a variable declaration. So the question is what to do about:

goto asdf;
using var file = File.Open("test");
asdf:;

If code after asdf is trying to use file then you'd get a Use of unassigned variable error so it's all good probably.

@ghord

This comment has been minimized.

Show comment
Hide comment
@ghord

ghord Dec 8, 2017

@mikedn Yet there are cases where using statement without variable declaration makes sense. I don't see why single-line using without variable declaration should be disallowed.

ghord commented Dec 8, 2017

@mikedn Yet there are cases where using statement without variable declaration makes sense. I don't see why single-line using without variable declaration should be disallowed.

@mikedn

This comment has been minimized.

Show comment
Hide comment
@mikedn

mikedn Dec 8, 2017

Yet there are cases where using statement without variable declaration makes sense. I don't see why single-line using without variable declaration should be disallowed.

Well, it is useful sometimes but IMO not often enough to warrant supporting this syntax. Besides, it seems natural to extend scoping so that "at the end of a scope objects stored in "using" variables declared in that scope are disposed". It's not so clear what's going on with using File.Open as there's no variable associated with it and thus it doesn't have anything obvious to do with scoping.

Perhaps using var _ = File.Open("test"); would be sufficient?

mikedn commented Dec 8, 2017

Yet there are cases where using statement without variable declaration makes sense. I don't see why single-line using without variable declaration should be disallowed.

Well, it is useful sometimes but IMO not often enough to warrant supporting this syntax. Besides, it seems natural to extend scoping so that "at the end of a scope objects stored in "using" variables declared in that scope are disposed". It's not so clear what's going on with using File.Open as there's no variable associated with it and thus it doesn't have anything obvious to do with scoping.

Perhaps using var _ = File.Open("test"); would be sufficient?

@ghord

This comment has been minimized.

Show comment
Hide comment
@ghord

ghord Dec 8, 2017

Personally, code like this makes this feature most appealing to me. Previously, you had to introduce complicated scopes. Don't even get me started on mixing try and catch here - that would be another scope.

   void Method()
   {
       logger.Log("Text");
       using logger.Indent();
       ...
       logger.Log("Indented text");
       ...
       using logger.Indent();
       ...
       logger.Log("Twice-indented text");
       ...
    }

Adding var _ everywhere would look even worse than normal scoped using.

ghord commented Dec 8, 2017

Personally, code like this makes this feature most appealing to me. Previously, you had to introduce complicated scopes. Don't even get me started on mixing try and catch here - that would be another scope.

   void Method()
   {
       logger.Log("Text");
       using logger.Indent();
       ...
       logger.Log("Indented text");
       ...
       using logger.Indent();
       ...
       logger.Log("Twice-indented text");
       ...
    }

Adding var _ everywhere would look even worse than normal scoped using.

@MkazemAkhgary

This comment has been minimized.

Show comment
Hide comment
@MkazemAkhgary

MkazemAkhgary Dec 8, 2017

Yet there are cases where using statement without variable declaration makes sense

use normal syntax in that case. using var _ = is very agonizing.

MkazemAkhgary commented Dec 8, 2017

Yet there are cases where using statement without variable declaration makes sense

use normal syntax in that case. using var _ = is very agonizing.

@mikedn

This comment has been minimized.

Show comment
Hide comment
@mikedn

mikedn Dec 8, 2017

Personally, code like this makes this feature most appealing to me. Previously, you had to introduce complicated scopes. Don't even get me started on mixing try and catch here - that would be another scope.

Ha ha, it makes sense in your example. Though personally I'm still not convinced about its usefulness. Perhaps that's because last time I encountered code with so much logging ceremony I did something that I considered to be immensely useful in making the code more readable - I deleted it all 😁.

mikedn commented Dec 8, 2017

Personally, code like this makes this feature most appealing to me. Previously, you had to introduce complicated scopes. Don't even get me started on mixing try and catch here - that would be another scope.

Ha ha, it makes sense in your example. Though personally I'm still not convinced about its usefulness. Perhaps that's because last time I encountered code with so much logging ceremony I did something that I considered to be immensely useful in making the code more readable - I deleted it all 😁.

@MkazemAkhgary

This comment has been minimized.

Show comment
Hide comment
@MkazemAkhgary

MkazemAkhgary Dec 8, 2017

@ghord I don't know what library you are using, but is there any method like Indent(int count) ? in that case you can prevent nested blocks.

by the way designers had in mind to make indentation implicit using this trick. I'm pretty sure if this syntax (single line using) was possible on time they developed it, they would've thought of something better to overcome with that issue.

so really they wanted to take advantage of blocks and make indentation implicit, but you want to also omit blocks.

MkazemAkhgary commented Dec 8, 2017

@ghord I don't know what library you are using, but is there any method like Indent(int count) ? in that case you can prevent nested blocks.

by the way designers had in mind to make indentation implicit using this trick. I'm pretty sure if this syntax (single line using) was possible on time they developed it, they would've thought of something better to overcome with that issue.

so really they wanted to take advantage of blocks and make indentation implicit, but you want to also omit blocks.

@alrz

This comment has been minimized.

Show comment
Hide comment
@alrz

alrz Dec 8, 2017

Contributor

Re: using x = ... syntax, and generally using expression;

I want to mention #218 which may want to use the exact same syntax (if ever happens). On the other hand, using var x = ... (or using scoped for that matter), is not ambiguous with that syntax.

Though the result might be confusing, so perhaps using scoped is a better choice here.

Contributor

alrz commented Dec 8, 2017

Re: using x = ... syntax, and generally using expression;

I want to mention #218 which may want to use the exact same syntax (if ever happens). On the other hand, using var x = ... (or using scoped for that matter), is not ambiguous with that syntax.

Though the result might be confusing, so perhaps using scoped is a better choice here.

@gulshan

This comment has been minimized.

Show comment
Hide comment
@gulshan

gulshan Dec 8, 2017

if a new keyword scoped is introduced, why not just use that without using? That will reduce confusion I think.

public static void CopyFile(string from, string into) 
{
    scoped var fromFile = File.Open(from, FileMode.Open, FileAccess.Read);
    scoped var toFile = File.Open(into, FileMode.Create, FileAccess.Write);
    // ... Perform actual file copy

    // Both FileStream objects are disposed at the end of the enclosing scope, 
    // in this case, the method declaration.
}

gulshan commented Dec 8, 2017

if a new keyword scoped is introduced, why not just use that without using? That will reduce confusion I think.

public static void CopyFile(string from, string into) 
{
    scoped var fromFile = File.Open(from, FileMode.Open, FileAccess.Read);
    scoped var toFile = File.Open(into, FileMode.Create, FileAccess.Write);
    // ... Perform actual file copy

    // Both FileStream objects are disposed at the end of the enclosing scope, 
    // in this case, the method declaration.
}
@TyOverby

This comment has been minimized.

Show comment
Hide comment
@TyOverby

TyOverby Dec 8, 2017

@gulshan: by piggybacking off of the using keyword, it is way easier to amend the grammar.

TyOverby commented Dec 8, 2017

@gulshan: by piggybacking off of the using keyword, it is way easier to amend the grammar.

@TonyValenti

This comment has been minimized.

Show comment
Hide comment
@TonyValenti

TonyValenti Dec 10, 2017

I think I would rather see C# keep track of whether a disposable object has leaked its scope and if it hasn't, have it automatically dispose it.

TonyValenti commented Dec 10, 2017

I think I would rather see C# keep track of whether a disposable object has leaked its scope and if it hasn't, have it automatically dispose it.

@Richiban

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban Sep 27, 2018

Never mind! I actually found it implemented on SharpLab in the "Pattern-based using" branch.

It is, as I suspected, equivalent to:

public void M()
{
    using (var a = new MyDisposable())
    {
        using (var b = new MyDisposable())
        {
            // Some code...
        }
    }
}

Richiban commented Sep 27, 2018

Never mind! I actually found it implemented on SharpLab in the "Pattern-based using" branch.

It is, as I suspected, equivalent to:

public void M()
{
    using (var a = new MyDisposable())
    {
        using (var b = new MyDisposable())
        {
            // Some code...
        }
    }
}
@jnm2

This comment has been minimized.

Show comment
Hide comment
@jnm2

jnm2 Sep 27, 2018

Contributor

It seems like there is only one implementation that makes sense: a try...finally for each Dispose call. (Equivalent to the using statements you have.)

Contributor

jnm2 commented Sep 27, 2018

It seems like there is only one implementation that makes sense: a try...finally for each Dispose call. (Equivalent to the using statements you have.)

@Richiban

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban Sep 27, 2018

As far as I can see at the moment, there's not really too much that can go wrong with this feature.

Sure, if you're not careful (and tend to have very long methods) you can make resources live longer than they ideally would otherwise.

But, that's only going to be a problem for about five minutes before devs learn that

void M()
{
	using var a = new MyDisposable();

	DoSomethingWith(a);

	SomethingElse();
}

and

void M()
{
	using(var a = new MyDisposable())
		DoSomethingWith(a);

	SomethingElse();
}

do not mean the same thing. I don't see this as confusing or difficult; after all, we had to learn that

void M()
{
	using(var a = new MyDisposable())
		DoSomethingWith(a);

	SomethingElse();
}

and

void M()
{
	using(var a = new MyDisposable())
	{
		DoSomethingWith(a);

		SomethingElse();
	}
}

are not the same thing either.

Richiban commented Sep 27, 2018

As far as I can see at the moment, there's not really too much that can go wrong with this feature.

Sure, if you're not careful (and tend to have very long methods) you can make resources live longer than they ideally would otherwise.

But, that's only going to be a problem for about five minutes before devs learn that

void M()
{
	using var a = new MyDisposable();

	DoSomethingWith(a);

	SomethingElse();
}

and

void M()
{
	using(var a = new MyDisposable())
		DoSomethingWith(a);

	SomethingElse();
}

do not mean the same thing. I don't see this as confusing or difficult; after all, we had to learn that

void M()
{
	using(var a = new MyDisposable())
		DoSomethingWith(a);

	SomethingElse();
}

and

void M()
{
	using(var a = new MyDisposable())
	{
		DoSomethingWith(a);

		SomethingElse();
	}
}

are not the same thing either.

@popcatalin81

This comment has been minimized.

Show comment
Hide comment
@popcatalin81

popcatalin81 Sep 28, 2018

But, that's only going to be a problem for about five minutes before devs learn that

The first production bug that happens because of this will make companies ban the usage of this feature entirely. If found out that developers will use a feature without understanding semantics, then action will be not to educate them but to ban the feature usage. That's what most companies do because they have incoming streams of developers and new juniors all the time. Banning something is the easy way out.

For simple use cases, this feature works great.

For more complex cases it can produce code that has such complex semantics that no one will want to debug such code. Imagine delay object lifetimes past several await statements, some which could be really long networks calls or even worse long-running operations persisted to durable storage.

Happy debugging the following

  • Getting error way too late while disposing of because of the async method
  • Creating resource starvation because of a nicer syntax which is used.
  • Creating hard to predict interactions due to disposing order not obvious in some scenarios

popcatalin81 commented Sep 28, 2018

But, that's only going to be a problem for about five minutes before devs learn that

The first production bug that happens because of this will make companies ban the usage of this feature entirely. If found out that developers will use a feature without understanding semantics, then action will be not to educate them but to ban the feature usage. That's what most companies do because they have incoming streams of developers and new juniors all the time. Banning something is the easy way out.

For simple use cases, this feature works great.

For more complex cases it can produce code that has such complex semantics that no one will want to debug such code. Imagine delay object lifetimes past several await statements, some which could be really long networks calls or even worse long-running operations persisted to durable storage.

Happy debugging the following

  • Getting error way too late while disposing of because of the async method
  • Creating resource starvation because of a nicer syntax which is used.
  • Creating hard to predict interactions due to disposing order not obvious in some scenarios
@Richiban

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban Sep 28, 2018

I definitely understand your point, but I think you're making too big a deal out of the potential downsides.

For more complex cases it can produce code that has such complex semantics

It's not complicated though, is it? The dispose method is called when when the variable in question falls out of scope. We all understand that there are Disposable objects in C#, and we certainly all understand scope...

I guess I'm just finding it very hard to come up with situations where this would actually cause any problems.

Certainly at the beginning I might give the following advice to devs: "Don't use implicit using scopes in async methods" but that would quickly fall by the wayside once people cotton on to the fact that, if the lifecycle of an object is important for some reason then they should take control of its lifecycle (i.e. define the using scope themselves with the existing syntax).

Richiban commented Sep 28, 2018

I definitely understand your point, but I think you're making too big a deal out of the potential downsides.

For more complex cases it can produce code that has such complex semantics

It's not complicated though, is it? The dispose method is called when when the variable in question falls out of scope. We all understand that there are Disposable objects in C#, and we certainly all understand scope...

I guess I'm just finding it very hard to come up with situations where this would actually cause any problems.

Certainly at the beginning I might give the following advice to devs: "Don't use implicit using scopes in async methods" but that would quickly fall by the wayside once people cotton on to the fact that, if the lifecycle of an object is important for some reason then they should take control of its lifecycle (i.e. define the using scope themselves with the existing syntax).

@Richiban

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban Sep 28, 2018

Also, WTF kind of a company bans features from their programming language of choice?

Richiban commented Sep 28, 2018

Also, WTF kind of a company bans features from their programming language of choice?

@popcatalin81

This comment has been minimized.

Show comment
Hide comment
@popcatalin81

popcatalin81 Sep 28, 2018

Also, WTF kind of a company bans features from their programming language of choice?

I got news for you ... mostly large enterprises, financial institutions, healthcare, governmental.I've seen var banned. I've seen Linq banned because it produces quote: unreadable code. I've seen ORM's banned because it produces SQL dba's can't control ... Don't worry you'll find them if you really look for them.

popcatalin81 commented Sep 28, 2018

Also, WTF kind of a company bans features from their programming language of choice?

I got news for you ... mostly large enterprises, financial institutions, healthcare, governmental.I've seen var banned. I've seen Linq banned because it produces quote: unreadable code. I've seen ORM's banned because it produces SQL dba's can't control ... Don't worry you'll find them if you really look for them.

@TonyValenti

This comment has been minimized.

Show comment
Hide comment
@TonyValenti

TonyValenti Sep 28, 2018

TonyValenti commented Sep 28, 2018

@Richiban

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban Sep 28, 2018

@TonyValenti

For example, we ban Async Void except for event handlers

I wouldn't call that "banning a feature", though. That's advice / guidance / style / whatever that is entirely correct. The only reason async void is even possible to write in C# is because it's necessary for event handlers. If your company had said "No one is allowed to use async" then, yeah, that would be a ban. And a terrible thing too.

Richiban commented Sep 28, 2018

@TonyValenti

For example, we ban Async Void except for event handlers

I wouldn't call that "banning a feature", though. That's advice / guidance / style / whatever that is entirely correct. The only reason async void is even possible to write in C# is because it's necessary for event handlers. If your company had said "No one is allowed to use async" then, yeah, that would be a ban. And a terrible thing too.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Sep 28, 2018

Contributor

@Richiban

Anytime I've seen it it's usually the result of an ex-developer-turned-manager who has long since stopped bothering to keep up with the changes in technology and finds anything newer than their frame of reference to be scary. I've seen this happen with LINQ (usually query syntax), var, lambdas, async/await, expression-bodied members and tuples/deconstruction. I've seen it in Java too with lambdas, streams, TWR, diamond syntax, Optional<T> (or anything even slightly monadic/functional) and especially var. In my experience I've found these policies to be based largely on the appearance of the code and not the behavior, the people that dictate where every curly brace and newline must appear.

I get the concern about the lifespan of disposable objects potentially lasting longer than necessary. This is particularly potentially egregious in async methods and iterators where the lifespan of the coroutine could be longer than your typical method call. These same concerns exist today with using blocks and the language does nothing to prevent you from making bad choices. The block forces you to think about it, although in most cases I bet people align them to be convenient with scoping rather than lifetimes. I'm not dismissing the concern, but I don't think it's as big of a deal or as common as expressed here.

Contributor

HaloFour commented Sep 28, 2018

@Richiban

Anytime I've seen it it's usually the result of an ex-developer-turned-manager who has long since stopped bothering to keep up with the changes in technology and finds anything newer than their frame of reference to be scary. I've seen this happen with LINQ (usually query syntax), var, lambdas, async/await, expression-bodied members and tuples/deconstruction. I've seen it in Java too with lambdas, streams, TWR, diamond syntax, Optional<T> (or anything even slightly monadic/functional) and especially var. In my experience I've found these policies to be based largely on the appearance of the code and not the behavior, the people that dictate where every curly brace and newline must appear.

I get the concern about the lifespan of disposable objects potentially lasting longer than necessary. This is particularly potentially egregious in async methods and iterators where the lifespan of the coroutine could be longer than your typical method call. These same concerns exist today with using blocks and the language does nothing to prevent you from making bad choices. The block forces you to think about it, although in most cases I bet people align them to be convenient with scoping rather than lifetimes. I'm not dismissing the concern, but I don't think it's as big of a deal or as common as expressed here.

@jaredpar

This comment has been minimized.

Show comment
Hide comment
@jaredpar

jaredpar Sep 28, 2018

Member

@popcatalin81

The first production bug that happens because of this will make companies ban the usage of this feature entirely.

How is this relevant to the discussion here? Companies make decisions about which features are / aren't allowed using good and bad logic all the time. This feature is not special in that regard.

For more complex cases it can produce code that has such complex semantics that no one will want to debug such code.

The generated code is exactly as complex as the using block code is today. Saying it's more complex is simply inaccurate.

Imagine delay object lifetimes past several await statements, some which could be really long networks calls or even worse long-running operations persisted to durable storage.

This problem exists today with using. This feature doesn't introduce any new behavior in this regard.

For simple use cases, this feature works great.

Indeed which is why we're interested in the feature. Also we are not alone here. F# has this exact feature and, unsurprisingly, it continues to be a popular language where developers are quite happy to use this feature.

Member

jaredpar commented Sep 28, 2018

@popcatalin81

The first production bug that happens because of this will make companies ban the usage of this feature entirely.

How is this relevant to the discussion here? Companies make decisions about which features are / aren't allowed using good and bad logic all the time. This feature is not special in that regard.

For more complex cases it can produce code that has such complex semantics that no one will want to debug such code.

The generated code is exactly as complex as the using block code is today. Saying it's more complex is simply inaccurate.

Imagine delay object lifetimes past several await statements, some which could be really long networks calls or even worse long-running operations persisted to durable storage.

This problem exists today with using. This feature doesn't introduce any new behavior in this regard.

For simple use cases, this feature works great.

Indeed which is why we're interested in the feature. Also we are not alone here. F# has this exact feature and, unsurprisingly, it continues to be a popular language where developers are quite happy to use this feature.

@popcatalin81

This comment has been minimized.

Show comment
Hide comment
@popcatalin81

popcatalin81 Oct 1, 2018

@jaredpar

How is this relevant to the discussion here?

The only point I was trying to make is the more features are introduced which potentially have unobvious semantics the more people will build restriction rules around them. My argument is that this is potentially one of those features.

Companies make decisions about which features are / aren't allowed using good and bad logic all the time.

Those decisions are most of the time, based on language feature semantics, sometime based on resulting performance outcomes. My preference as a user, would be that C# avoids unobvious semantics when applicable or posible.

The generated code is exactly as complex as the using block code is today. Saying it's more complex is simply inaccurate.

As complex yes. That's not the problem I was refering to. The probles is that it has different semantics for all cases where using blocks are not nested.

This problem exists today with using. This feature doesn't introduce any new behavior in this regard.

Then I'll repeat my example:

This is currently with using Blocks:

AppConfig config = null;
var serializer = new JsonSerializer() 
{
	TypeNameHandling = TypeNameHandling.Auto
};
using (var readStream = File.OpenText("appSettings.json"))
	config = (AppConfig)serializer.Deserialize(readStream, typeof(AppConfig));

config.EndpointUri = " ...."; // Get some new value from somewhere
using (var writeStream = File.CreateText("appSettings.json"))
	serializer.Serialize(writeStream, config);

And this is how it potentially looks like with using var:

AppConfig config = null;
var serializer = new JsonSerializer() 
{
	TypeNameHandling = TypeNameHandling.Auto
};
using var readStream = File.OpenText("appSettings.json");
config = (AppConfig)serializer.Deserialize(readStream, typeof(AppConfig));

config.EndpointUri = " ...."; // Get some new value from somewhere
using var writeStream = File.CreateText("appSettings.json");
serializer.Serialize(writeStream, config);

(The second one actually thows)

popcatalin81 commented Oct 1, 2018

@jaredpar

How is this relevant to the discussion here?

The only point I was trying to make is the more features are introduced which potentially have unobvious semantics the more people will build restriction rules around them. My argument is that this is potentially one of those features.

Companies make decisions about which features are / aren't allowed using good and bad logic all the time.

Those decisions are most of the time, based on language feature semantics, sometime based on resulting performance outcomes. My preference as a user, would be that C# avoids unobvious semantics when applicable or posible.

The generated code is exactly as complex as the using block code is today. Saying it's more complex is simply inaccurate.

As complex yes. That's not the problem I was refering to. The probles is that it has different semantics for all cases where using blocks are not nested.

This problem exists today with using. This feature doesn't introduce any new behavior in this regard.

Then I'll repeat my example:

This is currently with using Blocks:

AppConfig config = null;
var serializer = new JsonSerializer() 
{
	TypeNameHandling = TypeNameHandling.Auto
};
using (var readStream = File.OpenText("appSettings.json"))
	config = (AppConfig)serializer.Deserialize(readStream, typeof(AppConfig));

config.EndpointUri = " ...."; // Get some new value from somewhere
using (var writeStream = File.CreateText("appSettings.json"))
	serializer.Serialize(writeStream, config);

And this is how it potentially looks like with using var:

AppConfig config = null;
var serializer = new JsonSerializer() 
{
	TypeNameHandling = TypeNameHandling.Auto
};
using var readStream = File.OpenText("appSettings.json");
config = (AppConfig)serializer.Deserialize(readStream, typeof(AppConfig));

config.EndpointUri = " ...."; // Get some new value from somewhere
using var writeStream = File.CreateText("appSettings.json");
serializer.Serialize(writeStream, config);

(The second one actually thows)

@Richiban

This comment has been minimized.

Show comment
Hide comment
@Richiban

Richiban Oct 1, 2018

@popcatalin81 I appreciate your example, and indeed the two are not the same. What I don't get though is why you are so sure that people will think they're the same.

This is how I see this new feature being discussed between a newbie programmer and someone more experienced / up to date with new C# features:


Experienced dev: There's a new feature: a 'using statement' that you can sometimes use instead of a 'using block'. It kinda looks the same but there's no braces after.

Newbie: That's cool. I'll use it here. How do I write it, like this?

Experienced dev: That's right

Newbie: Great! That looks nice. Hang on, since there's no braces any more, when does this object get disposed?

Experienced dev: At the end of the block. So, in this case, at the end of the method.

Newbie: Right. So, that's not the same as what's here, then? This gets disposed immediately.

Experienced dev: Correct, it's not the same.

Newbie: Hmm, so I don't want this, do I?

Experienced dev: No, you don't.


Am I being too optimistic?

Richiban commented Oct 1, 2018

@popcatalin81 I appreciate your example, and indeed the two are not the same. What I don't get though is why you are so sure that people will think they're the same.

This is how I see this new feature being discussed between a newbie programmer and someone more experienced / up to date with new C# features:


Experienced dev: There's a new feature: a 'using statement' that you can sometimes use instead of a 'using block'. It kinda looks the same but there's no braces after.

Newbie: That's cool. I'll use it here. How do I write it, like this?

Experienced dev: That's right

Newbie: Great! That looks nice. Hang on, since there's no braces any more, when does this object get disposed?

Experienced dev: At the end of the block. So, in this case, at the end of the method.

Newbie: Right. So, that's not the same as what's here, then? This gets disposed immediately.

Experienced dev: Correct, it's not the same.

Newbie: Hmm, so I don't want this, do I?

Experienced dev: No, you don't.


Am I being too optimistic?

@popcatalin81

This comment has been minimized.

Show comment
Hide comment
@popcatalin81

popcatalin81 Oct 1, 2018

@Richiban
I don't know how to put this, other than this:

Every mistake a developer could make (allowed by the language) I've made it. and seen others make it (countless times). That does not mean the features were bad intrinsically for allowing you to make mistakes.

It's a matter of preference: using block has better semantics than using var declaration and for me, the syntax improvements do not seem to be as dramatic to worth it.

Am I being too optimistic?

If you think all interactions and discussions will resolve like that, I'd say yes. Some will some will resolve completely differently.

popcatalin81 commented Oct 1, 2018

@Richiban
I don't know how to put this, other than this:

Every mistake a developer could make (allowed by the language) I've made it. and seen others make it (countless times). That does not mean the features were bad intrinsically for allowing you to make mistakes.

It's a matter of preference: using block has better semantics than using var declaration and for me, the syntax improvements do not seem to be as dramatic to worth it.

Am I being too optimistic?

If you think all interactions and discussions will resolve like that, I'd say yes. Some will some will resolve completely differently.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Oct 1, 2018

@popcatalin81,

(The second one actually thows)

This is why I'm personally struggling to see why you think the new syntax will cause problems. There's no "spooky at a distance " problem occurring: the failure is contained within the method and is deterministic as it will always consistently throw. So such a bug is readily caught by an automated test or when the method is called during manual testing.

Usings dispose at the end of the block. For existing usings, that block immediately follows the using statement. For implicit usings, it's at the end of the containing block. This seems an incredibly simple rule to understand compared with many existing, popular features in C#. So I really don't see why it'll be an issue.

DavidArno commented Oct 1, 2018

@popcatalin81,

(The second one actually thows)

This is why I'm personally struggling to see why you think the new syntax will cause problems. There's no "spooky at a distance " problem occurring: the failure is contained within the method and is deterministic as it will always consistently throw. So such a bug is readily caught by an automated test or when the method is called during manual testing.

Usings dispose at the end of the block. For existing usings, that block immediately follows the using statement. For implicit usings, it's at the end of the containing block. This seems an incredibly simple rule to understand compared with many existing, popular features in C#. So I really don't see why it'll be an issue.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Oct 1, 2018

I've seen Linq banned

And yet, Linq was still good to do. The presence of companies willing to ban over language features does not mean those language features should not be done. After all, i'm sure for every language feature, you could find some codebase that has banned it. But we obviously still work on new language features despite that.

CyrusNajmabadi commented Oct 1, 2018

I've seen Linq banned

And yet, Linq was still good to do. The presence of companies willing to ban over language features does not mean those language features should not be done. After all, i'm sure for every language feature, you could find some codebase that has banned it. But we obviously still work on new language features despite that.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Oct 1, 2018

I get the concern about the lifespan of disposable objects potentially lasting longer than necessary. This is particularly potentially egregious in async methods and iterators where the lifespan of the coroutine could be longer than your typical method call. These same concerns exist today with using blocks and the language does nothing to prevent you from making bad choices.

Indeed, i made this very mistake in Roslyn in the Sqlite layer. I made an async call while a disposable resource was being held. Should we ban async+disposal+usings? No. We just need to be careful when combining these things.

CyrusNajmabadi commented Oct 1, 2018

I get the concern about the lifespan of disposable objects potentially lasting longer than necessary. This is particularly potentially egregious in async methods and iterators where the lifespan of the coroutine could be longer than your typical method call. These same concerns exist today with using blocks and the language does nothing to prevent you from making bad choices.

Indeed, i made this very mistake in Roslyn in the Sqlite layer. I made an async call while a disposable resource was being held. Should we ban async+disposal+usings? No. We just need to be careful when combining these things.

@popcatalin81

This comment has been minimized.

Show comment
Hide comment
@popcatalin81

popcatalin81 Oct 1, 2018

Indeed, i made this very mistake in Roslyn in the Sqlite layer. I made an async call while a disposable resource was being held. Should we ban async+disposal+usings? No. We just need to be careful when combining these things.

That's exactly the point I was trying to make: It's too easy to combine using var with async. Thea feature almost facilitates combining them.

popcatalin81 commented Oct 1, 2018

Indeed, i made this very mistake in Roslyn in the Sqlite layer. I made an async call while a disposable resource was being held. Should we ban async+disposal+usings? No. We just need to be careful when combining these things.

That's exactly the point I was trying to make: It's too easy to combine using var with async. Thea feature almost facilitates combining them.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Oct 1, 2018

That's exactly the point I was trying to make: It's too easy to combine using var with async. Thea feature almost facilitates combining them.

So... the don't combine them. I mean, we do this all the time in other arenas. in places where allocation costs are problematic, we don't allow allocation-heavy features. In places where dispatch costs are problematic, we don't use virtual/interface dispatch. etc. etc. Not every language feature is appropriate in every codebase (or at any point in any particular codebase).

We have rules all over hte place for things you should be careful of because they might cause a problem. As i pointed out, async+disposal is already a problem, and it doesn't seem to be any worse here. In both cases you have to think about what's going on and set things up properly. So nothing really changes.

As i pointed out, not having "using var" didn't save me. The problem exists independently of the new language construct.

CyrusNajmabadi commented Oct 1, 2018

That's exactly the point I was trying to make: It's too easy to combine using var with async. Thea feature almost facilitates combining them.

So... the don't combine them. I mean, we do this all the time in other arenas. in places where allocation costs are problematic, we don't allow allocation-heavy features. In places where dispatch costs are problematic, we don't use virtual/interface dispatch. etc. etc. Not every language feature is appropriate in every codebase (or at any point in any particular codebase).

We have rules all over hte place for things you should be careful of because they might cause a problem. As i pointed out, async+disposal is already a problem, and it doesn't seem to be any worse here. In both cases you have to think about what's going on and set things up properly. So nothing really changes.

As i pointed out, not having "using var" didn't save me. The problem exists independently of the new language construct.

@popcatalin81

This comment has been minimized.

Show comment
Hide comment
@popcatalin81

popcatalin81 Oct 3, 2018

So... the don't combine them.

Don't worry I won't do it myself.

But I'll the one called to fix weird bugs because some dev has used a feature without understanding the semantics. But that's not a problem really. I'm a consultant, I make money when others screw up ... (That's why I'm skeptic about this feature, perhaps even biased)

popcatalin81 commented Oct 3, 2018

So... the don't combine them.

Don't worry I won't do it myself.

But I'll the one called to fix weird bugs because some dev has used a feature without understanding the semantics. But that's not a problem really. I'm a consultant, I make money when others screw up ... (That's why I'm skeptic about this feature, perhaps even biased)

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Oct 3, 2018

But I'll the one called to fix weird bugs because some dev has used a feature without understanding the semantics.

That's an issue with all language features :)

Don't understand 'async'? Then you're going to have weird bugs. Don't understand lambdas, or generics, or 'ref', or operators, or structs, or inheritance, or stacks, or memory, or etc. etc. etc.? Then you're going to have weird bugs. We don't stop moving the language forward just because people there exist people who use the language without spending hte time to understand what's going on. :)

Heck, if you don't understand 'using' today, you're going to have issues. But life moves on and the language still improves so that people who do learn and understand can have a better experience :)

CyrusNajmabadi commented Oct 3, 2018

But I'll the one called to fix weird bugs because some dev has used a feature without understanding the semantics.

That's an issue with all language features :)

Don't understand 'async'? Then you're going to have weird bugs. Don't understand lambdas, or generics, or 'ref', or operators, or structs, or inheritance, or stacks, or memory, or etc. etc. etc.? Then you're going to have weird bugs. We don't stop moving the language forward just because people there exist people who use the language without spending hte time to understand what's going on. :)

Heck, if you don't understand 'using' today, you're going to have issues. But life moves on and the language still improves so that people who do learn and understand can have a better experience :)

@popcatalin81

This comment has been minimized.

Show comment
Hide comment
@popcatalin81

popcatalin81 Oct 3, 2018

That's an issue with all language features. Don't understand 'async'? Then you're going to have weird bugs.

Yes. But in this case, there simply is a better option: using block, using var is a simply a worse version of that, except syntax ... maybe ...

popcatalin81 commented Oct 3, 2018

That's an issue with all language features. Don't understand 'async'? Then you're going to have weird bugs.

Yes. But in this case, there simply is a better option: using block, using var is a simply a worse version of that, except syntax ... maybe ...

@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Oct 4, 2018

@popcatalin81 Wrapping the whole function with using block into multiple of stacking pyramid is not better option. It not convenient and mess the code. Taking more time and energy to understand the code. While implicit using treat the disposing like it was a struct, just dispose when the stack pop out. Use it when we don't need to care just to ensuring disposal. And not affect the whole code like using block

Thaina commented Oct 4, 2018

@popcatalin81 Wrapping the whole function with using block into multiple of stacking pyramid is not better option. It not convenient and mess the code. Taking more time and energy to understand the code. While implicit using treat the disposing like it was a struct, just dispose when the stack pop out. Use it when we don't need to care just to ensuring disposal. And not affect the whole code like using block

@jnm2

This comment has been minimized.

Show comment
Hide comment
@jnm2

jnm2 Oct 4, 2018

Contributor

multiple of stacking pyramid

The usual phrase is https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming)

Contributor

jnm2 commented Oct 4, 2018

multiple of stacking pyramid

The usual phrase is https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming)

@popcatalin81

This comment has been minimized.

Show comment
Hide comment
@popcatalin81

popcatalin81 Oct 4, 2018

@popcatalin81 Wrapping the whole function with using block into multiple of stacking pyramid is not better option.

@Thaina so you prefer a hidden Pyramid? opposed to a visible one? It's easier to understand an implied pyramid than an explicit one?

using var does not eliminate the pyramid it just hides it. But if you want it eliminated syntactically, using block already has this option, because you don't need to add brackets for each statement.

So let's see:

using block

  • Controlled nesting. (Can have un-nested using blocks in a single method)
  • Creates a scope for variables (Better analysis of lifetime)
  • Pyramid code can be avoided for nested usings by omititing braces

using var

  • Uncontrolled nesting. All declarations are nested at parent scope level. Un-nested using blocks in same scope are not posible.
  • Does not create a scope but uses the parent scope. Parent scope can be extended by adding more calls or code, which automatically prolongs disposable resource lifetime. It createa a dependency between method length, number of calls number of disposable resources, async calls, all affect each other negativelly.
  • Pyramid code does not exist at syntax level but there's a single giant piramid at logical level.
  • Potentially creates a dependency between variable declaration order. Switching variable declaration order now becomes an breaking change.

popcatalin81 commented Oct 4, 2018

@popcatalin81 Wrapping the whole function with using block into multiple of stacking pyramid is not better option.

@Thaina so you prefer a hidden Pyramid? opposed to a visible one? It's easier to understand an implied pyramid than an explicit one?

using var does not eliminate the pyramid it just hides it. But if you want it eliminated syntactically, using block already has this option, because you don't need to add brackets for each statement.

So let's see:

using block

  • Controlled nesting. (Can have un-nested using blocks in a single method)
  • Creates a scope for variables (Better analysis of lifetime)
  • Pyramid code can be avoided for nested usings by omititing braces

using var

  • Uncontrolled nesting. All declarations are nested at parent scope level. Un-nested using blocks in same scope are not posible.
  • Does not create a scope but uses the parent scope. Parent scope can be extended by adding more calls or code, which automatically prolongs disposable resource lifetime. It createa a dependency between method length, number of calls number of disposable resources, async calls, all affect each other negativelly.
  • Pyramid code does not exist at syntax level but there's a single giant piramid at logical level.
  • Potentially creates a dependency between variable declaration order. Switching variable declaration order now becomes an breaking change.
@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Oct 4, 2018

@popcatalin81 It just a perspective. If you call it hidden pyramid then async/await is already one. It actually a callback pyramid that was flatten out by keyword

In my perspective it not a hidden pyramid because you shove all responsible stack to the parent scope. In intuitively the same as struct allocation on stack. It flatten out the all the pyramid into scope and having the same place to dispose, at the end of scope, and just in reverse order, which it should

It like you have array as stack. You don't need to really stack things in memory, it just abstraction to the LIFO process. And this make consistency for both code and memory

Hidden pyramid is not pyramid. Flatten pyramid is not pyramid. Because what make pyramid is its shape, not because the order of operation it got

Thaina commented Oct 4, 2018

@popcatalin81 It just a perspective. If you call it hidden pyramid then async/await is already one. It actually a callback pyramid that was flatten out by keyword

In my perspective it not a hidden pyramid because you shove all responsible stack to the parent scope. In intuitively the same as struct allocation on stack. It flatten out the all the pyramid into scope and having the same place to dispose, at the end of scope, and just in reverse order, which it should

It like you have array as stack. You don't need to really stack things in memory, it just abstraction to the LIFO process. And this make consistency for both code and memory

Hidden pyramid is not pyramid. Flatten pyramid is not pyramid. Because what make pyramid is its shape, not because the order of operation it got

@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Oct 4, 2018

@popcatalin81 And I think you misunderstand the most important point. That we don't deprecate or stop using the using block

I would still using the using block where I want a variable to stay only in small scope and should be to cleanup immediately. But using var is useful anywhere that we need to use that variable in the whole function. It not that we need to choose one. It just an option we have to make code more neatly

In fact I think it would be common to have using var inside using block such this

using(var something = LoadSomeThing())
{
    // Do something with something

    if(needAnotherThing)
    {
        using var anotherThing = LoadAnotherThing(); // can avoid pyramid
        // Do anything that need both something and anotherThing
    } // anotherThing dispose here

    // do some more thing relate to something but don't need anotherThing
}

// do some more thing not relate to something or anotherThing

Like this

Thaina commented Oct 4, 2018

@popcatalin81 And I think you misunderstand the most important point. That we don't deprecate or stop using the using block

I would still using the using block where I want a variable to stay only in small scope and should be to cleanup immediately. But using var is useful anywhere that we need to use that variable in the whole function. It not that we need to choose one. It just an option we have to make code more neatly

In fact I think it would be common to have using var inside using block such this

using(var something = LoadSomeThing())
{
    // Do something with something

    if(needAnotherThing)
    {
        using var anotherThing = LoadAnotherThing(); // can avoid pyramid
        // Do anything that need both something and anotherThing
    } // anotherThing dispose here

    // do some more thing relate to something but don't need anotherThing
}

// do some more thing not relate to something or anotherThing

Like this

@theunrepentantgeek

This comment has been minimized.

Show comment
Hide comment
@theunrepentantgeek

theunrepentantgeek Oct 4, 2018

It seems to me that this discussion is starting to repeat the same points over and over again.

I've seen other discussions in this repo where one of the parties has assumed that "disagreement" equals "not understanding", resulting in a tremendous amount of loud repetition.

I fear this issue might be heading in the same direction. I fervently hope it's not, and I would like to derail that if possible.

@popcatalin81 I'm just a random developer, participating here just because I find the process of language design endlessly fascinating. Please be assured that I have understood and appreciated the points you're making - and if I do, I'm pretty sure the LDM folks who make the decisions do as well.

theunrepentantgeek commented Oct 4, 2018

It seems to me that this discussion is starting to repeat the same points over and over again.

I've seen other discussions in this repo where one of the parties has assumed that "disagreement" equals "not understanding", resulting in a tremendous amount of loud repetition.

I fear this issue might be heading in the same direction. I fervently hope it's not, and I would like to derail that if possible.

@popcatalin81 I'm just a random developer, participating here just because I find the process of language design endlessly fascinating. Please be assured that I have understood and appreciated the points you're making - and if I do, I'm pretty sure the LDM folks who make the decisions do as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment