dotnet / csharplang

The official repo for the design of the C# programming language
11.48k stars 1.02k forks source link

Champion "Implicitly scoped using statement" (16.3, Core 3) #1174

Open gafter opened 6 years ago

gafter commented 6 years ago

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 https://github.com/dotnet/csharplang/issues/114 and https://github.com/dotnet/csharplang/issues/114#issuecomment-349773994

LDM history:

popcatalin81 commented 6 years ago

What I don't quite follow is: What exactly makes you think this particular feature would be easy to misuse?

It has more complicated semantics than using block. It has different semantics than using block. (It took developers years to learn the difference between 'throw ex' and 'throw;' and that mistake is still being made even with the prevalence of Resharper and Code Analyzers. I still have to explain the difference between async void and async Task and people still have a hard time understanding. 'lock' is still user wrongly (the designers regret was calling it lock, which imply "different semantics") ... It comes down to semantics, where "different" or "non-obvious" is a clear path to misuse.

Thaina commented 6 years ago

@popcatalin81 In my opinion, implicit using is ten times more easier to understand than difference between async void and async Task. One thing is because when you change from block syntax to implicit syntax it then change the scope of variable and that could cause error in some case especially when there was duplicate variable name

HaloFour commented 6 years ago

@popcatalin81

People have to learn how to use a feature. Some people will get that wrong. If preventing that was a measure to which all features were required then there would be no programming languages.

The opportunity for abuse here is low. As is the opportunity for the method to do something unintended. Dispose is still called on each of the resources. It's still called in the same order as if you had defined them in using blocks instead of statements. The only difference is when, and in my opinion the majority of the time that doesn't matter. Sure, maybe a resource will remain allocated for longer than expected. In my opinion the majority of the time that doesn't matter in the slightest. But the concept of scoping to the end of the current block isn't controversial. It's how literally every local variable already works.

Neme12 commented 6 years ago

The only difference is when, and in my opinion the majority of the time that doesn't matter. Sure, maybe a resource will remain allocated for longer than expected. In my opinion the majority of the time that doesn't matter in the slightest

Are you actually arguing for using this feature even in cases where the disposal currently happens earlier than at the end of the scope, simply because it has a nicer syntax?

This is exactly the problem I was calling out with this feature. You're making excuses for writing poorer code simply because the syntax is a little nicer/shorter.

jnm2 commented 6 years ago

@Neme12 The current using statement already pushes me to write poorer code in that sense. There are situations that keep coming up where it would make sense to dispose early instead of in reverse order as the using scopes end.

popcatalin81 commented 6 years ago

@HaloFour

Take the following:

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

This is a simple case, where actually switching will get you an easy way out because You'll get an exception. Or will you not get the exception?

What will the next code do? Throw, don't throw, overwrite the file?

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

What about this code?

using var nw = new NorthwindEntities();
var products = (from p in nw.Products [where ....] select p).ToList();
var categories = (from p in nw.Categories [where ....] select p).ToList();

using var productService = new ProductService();
var updatedProducts = await productService.UpdateAsync(products);

using var categorieService = new ProductService();
var updatedCategories = await categorieService.UpdateAsync(categories);

is the lifetime of objects optimal?

Neme12 commented 6 years ago

Yes, in most cases, the difference will be small, however I don't like how easy this makes writing the poorer code by default.

HaloFour commented 6 years ago

@Neme12

Are you actually arguing for using this feature even in cases where the disposal currently happens earlier than at the end of the scope, simply because it has a nicer syntax?

I'm arguing that the majority of time it doesn't matter. Just like it doesn't for GC. You don't null all of your reference variables at the end of a block to make them eligible for GC slightly earlier, do you? I didn't think so.

jnm2 commented 6 years ago

@HaloFour

You don't null all of your reference variables at the end of a block to make them eligible for GC slightly earlier, do you? I didn't think so.

The runtime keeps them alive after their last read? I was sure this wasn't the case. Why have I had to use GC.KeepAlive(localVar) then?

Thaina commented 6 years ago

@Neme12 I think that's wrong, you need to indent the whole block just to have it dispose at the end of parent scope is the poorer code by default

Neme12 commented 6 years ago

I think that's wrong, you need to indent the whole block just to have it dispose at the end of parent scope is the poorer code by default

By poorer code, I don't mean poorer syntax. I'm arguing that the problem is introducing nicer syntax for poorer code.

popcatalin81 commented 6 years ago

I'm arguing that the problem is introducing nicer syntax for poorer code.

For me, that sums up this entire feature.

HaloFour commented 6 years ago

@popcatalin81

Sums up LINQ, too. Easier syntax is certainly worth it when the tradeoff is minimal in most circumstances.

Neme12 commented 6 years ago

I'm arguing that the majority of time it doesn't matter.

What makes you think that? I just did a quick search for using statements in a desktop app and for the majority of them, it does matter, because they're surrounded by awaits.

Consider something like this:

            using (loadingStack.Activate())
                location = await locationTask;

            await blahblah..

I would never ever consider an implicit scoped using, even if the disposal happened to coincide with the end of the scope. Because then what if I need to add a new statement? Now I have to remember to go back and rewrite the using variable to a using block. And I bet sometimes I won't even notice that I should go back and do this, because it's not local to what I'm typing right now.

            using loadingStack.Activate();
            await blahblahblah...; // all good

            // now I'm writing more code here (that doesn't belong inside the "using")

and need to go back and rewrite the whole damn thing. most people 1. won't do that because they'll forget or 2. won't care about doing that, because hey, this looks nicer, and i don't want to keep changing between 2 different forms depending on whether i currently have more statements or not

jnm2 commented 6 years ago

@Neme12 I get the argument, but I've become agnostic on this in the last few months. I can't escape the thought that RAII behavior will just be a thing that people come to understand and look for. You already have to look up and down at the context of the other operations in a method; you don't always have an extra indent to remind you to do that.

In other words, I can see myself trying this out and not hating it.

Neme12 commented 6 years ago

Also, this make me think about the new async using for C# 8. Will there be an async form of this? How to disambiguate that with an await expression?

I don't quite like the fact that you'd have an await keyword at the declaration but the awaiting would actually happen at the end of the scope.

HaloFour commented 6 years ago

@Neme12

What makes you think that? I just did a quick search for using statements in a desktop app and for the majority of them, it does matter, because they're surrounded by awaits.

One app does not an ecosystem make. My experience is the opposite.

Neme12 commented 6 years ago

One app does not an ecosystem make.

Which is why I mentioned that I have the opposite experience than you do, and you can't draw any conclusions about this not being a problem from your own experience.

Richiban commented 6 years ago

Reading through these comments I'm becoming a little confused as to how the implicit scopes exactly will lay out.

Is the following:

public void M()
{
    using a = new MyDisposable();
    using b = new MyDisposable();

    // Some code...
}

Equivalent to this?

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

Or something else? Perhaps:

public void M()
{
    try
    {
        var a = new MyDisposable();
        var b = new MyDisposable();

        // Some code...
    }
    catch(Exception)
    {
        a.Dispose();
        b.Dispose();
    }
}
Richiban commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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

Richiban commented 6 years ago

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 6 years ago

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

popcatalin81 commented 6 years ago

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 commented 6 years ago

Hi Richard, I think lots of companies do. Sometimes it is an outright ban and sometimes it is required guidance. At my company we call that our "coding style". For example, we ban Async Void except for event handlers.

On Fri, Sep 28, 2018 at 8:46 AM Richard Gibson notifications@github.com wrote:

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

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/dotnet/csharplang/issues/1174#issuecomment-425440372, or mute the thread https://github.com/notifications/unsubscribe-auth/AM-qVmscrgqxxMmaBAww6wCuKxYaqDxUks5ufigcgaJpZM4Q4v1B .

-- Tony Valenti

Schedule a meeting with me https://Fasterlaw.com/schedule-meeting

Richiban commented 6 years ago

@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 commented 6 years ago

@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 commented 6 years ago

@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 commented 6 years ago

@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 commented 6 years ago

@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 commented 6 years ago

@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 commented 6 years ago

@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 commented 6 years ago

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 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

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 commented 6 years ago

@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 commented 6 years ago

multiple of stacking pyramid

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

popcatalin81 commented 6 years ago

@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

using var

Thaina commented 6 years ago

@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 6 years ago

@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 commented 6 years ago

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.

int19h commented 5 years ago

I hope that whatever syntax and semantics this proposal evolves into, will take into account the possibility of future evolution covering more than just local variables - and in particular, implicitly disposed fields / implicit implementation of IDispose (in the vein of C++/CLI destructors).

Note, I'm not saying that it should be a part of this proposal. Only that if such a direction is later taken, whatever is done here would serve as a natural basis - so it's best to pick an accommodating syntax today, so as to not have regrets over it in a few years.

With that in mind, and in the spirit of minor bikeshedding, how about owned as a keyword indicating that the variable should be automatically disposed once out of scope? While using is the historical choice of keyword for this in C#, and it's not unreasonable to try to reuse it, one could also argue that it's already unduly overloaded, and in any case makes more sense as an explicit block (with explicit block syntax, it reads as "using this, do that, [and then get rid of it]" - which it wouldn't as a modifier on a variable). On the other hand, resource ownership is really what this is all about - and with the growing popularity of Rust, it's more common to see the term used explicitly more often. So, as a keyword, it concisely and clearly captures the intent, more so than "using" or even "scoped" (all variables are scoped, after all!).

Here's the code snippet from the scoped proposal, restyled in this manner:

public static void CopyFile(string from, string into) 
{
    owned var fromFile = File.Open(from, FileMode.Open, FileAccess.Read);
    owned 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.
}

(And then perhaps var could be made implicit if no type is specified? There doesn't seem to be any particular reason to force it, and owned file reads better than owned var file.)

Such syntax would also extend naturally to ownership of objects referenced by another object via its fields, if some future proposal decides to go there, e.g.:

owner class Image {
    private owned FileStream stream;
    ...
}
kkm000 commented 5 years ago

FWIW, we have had this feature in F# for years, since v3, I believe, happy to have it, and see no troubles from it at all!

The syntax exactly follows that of the binding, only with a different keyword for scoped disposal (let to use ≈ var to using). I do not see absolutely any downside in introducing the 'using' [ var '=' ] expression ';' syntax; such a thing is nearly impossible not to grok to anyone who understands how var and the using(...) work, in my opinion.

scoped sounds about as useful as the keyword auto in C¹. You do know that C has the auto keyword, right? (Hint: auto int n; is same as int n;).

I am not going to quote examples to keep flame low, but I cannot help remarking that I've read many objections here that sound no better than patronizing. Please. C# is not intended as language to teach kindergartners how to program. A language that has no features that can potentially be misused is either not Turing-complete, or Haskell.

Cannot put it into words better than @CyrusNajmabadi did:

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. [...]
Heck, if you don't understand 'using' today, you're going to have issues.

Amen to that. Once you've learnt that await captures the synchronization context, unless you add a .ConfigureAwait(false) (false. FALSE, for gremlins sake!!!) to every single await you write, lest you run into supremely entertaining deadlocks on random contexts that you did not even know to exist, you've learnt everything you need to know. Which is do not assume anything whatsoever about any new feature, RTFM thrice, and then use the feature responsibly.

And "some devs" misuse the assignment operator too², but I still want it in the language, if you do not mind :-)


¹ confusingly, not to be confused with the auto in C++.
² I can think of List<MyStructType> a; ...; a[5].Answer = 42;, for example.

int19h commented 5 years ago

using X = Y is already valid syntax in C# that means something else entirely (it introduces a namespace or a type alias).