Open gafter opened 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.
@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
@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.
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.
@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.
@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?
Yes, in most cases, the difference will be small, however I don't like how easy this makes writing the poorer code by default.
@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.
@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?
@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
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.
I'm arguing that the problem is introducing nicer syntax for poorer code.
For me, that sums up this entire feature.
@popcatalin81
Sums up LINQ, too. Easier syntax is certainly worth it when the tradeoff is minimal in most circumstances.
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
@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.
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.
@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.
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.
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();
}
}
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...
}
}
}
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.)
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.
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
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).
Also, WTF kind of a company bans features from their programming language of choice?
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.
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
@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
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.
@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.
@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 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 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,
(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.
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.
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.
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.
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.
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)
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 :)
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 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
multiple of stacking pyramid
The usual phrase is https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming)
@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
@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
@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
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.
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;
...
}
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.
using X = Y
is already valid syntax in C# that means something else entirely (it introduces a namespace or a type alias).
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 theusing
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: