Can't use C# 8.0 using declaration with a discard #8605
Replies: 97 comments 11 replies
-
@mavasani for the code fix bug |
Beta Was this translation helpful? Give feedback.
-
We should probably just consider a using variable declaration as an effective read of the underlying variable, so the assigned value is not deemed as unused. I can send a PR for it tomorrow. |
Beta Was this translation helpful? Give feedback.
-
Tagging @chsienki @AlekseyTs and @333fred. The false positive from the IDE unused value assignment analyzer is due to missing IOperation support and hence missing ControlFlowGraph support for using variable declarations #dotnet/roslyn/issues/32100. Analyzer is performing flow analysis on top of CFG to flag assignments whose target is never read or referenced. CFG for this code has a local variable declaration wrapped with an Operation.None and there is no subsequent local reference operation in the CFG. Implementing CFG support for this node would ensure there is an implicitly generated ILocalReferenceOperation for the compiler generated dispose invocation, which would remove the false positive from this analyzer and hence no suggestion to use discard would be offered. |
Beta Was this translation helpful? Give feedback.
-
Actually, the analyzer/code fix to recommend using discard should be fixed in VS2019 Preview4 with |
Beta Was this translation helpful? Give feedback.
-
Confirmed that unused value diagnostic + use discard code fix is offered in VS2019 Preview2 but not in latest VS2019 dogfood builds, and should be fixed in upcoming Preview4 release. |
Beta Was this translation helpful? Give feedback.
-
@RayKoopa the new using declaration feature only works for explicit variable declarations, you can't apply Essentially using (var x = ...)
{
x.DoSomething();
} can be re-written as using var x = ...
x.DoSomething(); but using(SomeExpression())
{
} Can't be directly translated, as there's no declaration in the statement. You can however, assign it to a variable and it'll work, even if you don't use the variable for anything else. As you saw some of the tooling in the previews is a little out of sync with the newest language features at the moment, hence the unused diagnostic in VS, but the compiler will correctly track that it's actually used. |
Beta Was this translation helpful? Give feedback.
-
Thanks for letting me know @chsienki! I noticed I can even go as far as using var _ = ... to "pseudo" discard the variable here, and it would still compile. That's really sufficient, just wanted to make sure it was done on purpose like this and is not something like an oversight. |
Beta Was this translation helpful? Give feedback.
-
This would be quite useful. I'd love to be able to use using declarations with discards. |
Beta Was this translation helpful? Give feedback.
-
There is also the problem of multiple discards. For now the only way is to add more underscores (actually giving different names to the variables as they are not really discards):
|
Beta Was this translation helpful? Give feedback.
-
This could be useful for RAII-style lock acquisition; for example, one would be able to do void M()
{
using _ = myLock.Acquire() // rest of scope is protected by lock
// ...
} instead of having to implement something like #2451. |
Beta Was this translation helpful? Give feedback.
-
This also makes it painful to use var foo = new Foo(); // Foo : IAsyncDisposable
await using var unused1 = foo.ConfigureAwait(false);
// ... In order to avoid declaring unused variables, you must go back to a var foo = new Foo(); // Foo : IAsyncDisposable
await using (foo.ConfigureAwait(false))
{
// ...
} |
Beta Was this translation helpful? Give feedback.
-
What about this syntax for discarding the value? using myLock.Acquire();
// lock-protected code
// Also, don't forget about IAsyncDisposable!
await using myLock.AcquireAsync(); |
Beta Was this translation helpful? Give feedback.
-
@TehPers, the problem is that: using (myLock.Acquire()); is already valid C# code and means something slightly different. Namely dispose the resource right now and not at the end of the outer block. |
Beta Was this translation helpful? Give feedback.
-
Our use case for this is Serilog's
instead of:
|
Beta Was this translation helpful? Give feedback.
-
From my understanding, it's not that it's impossible to resolve the apparent ambiguity (and even if a usage does happen to be ambiguous somehow, I think there's a general understanding that an error would be fine). Rather, as I understand it, the issue here is that because a statement at the top of a file of the form So (again, as I understand it) if this were the kind of feature that would REALLY add a lot of value, then I'm sure the team would be able to overcome the issues and get something that works just perfectly (which is why I imagine this hasn't been outright closed as "rejected"). As the problem it solves is really just a minor inconvenience in the grand scheme of things, however, I'm not surprised that it has taken this long without a resolution. |
Beta Was this translation helpful? Give feedback.
-
While I understand and applaud the push to reduce unnecessary ceremony, fixing this would improve my developer experience far more than top-level statements ever will. |
Beta Was this translation helpful? Give feedback.
-
Probably a non starter, but changing the namespace using to |
Beta Was this translation helpful? Give feedback.
-
Yes. That would be a non-starter. That would also introduce its own set of issues as things like |
Beta Was this translation helpful? Give feedback.
-
Agreed, besides being next to useless for experienced developers, top level statements are already an ugly hack and come with a huge list of caveats which need to be considered when programming in that context. I don't think adding yet another caveat to that list of pitfalls will be much of an issue, for a feature that is hugely useful in actual real-life code. |
Beta Was this translation helpful? Give feedback.
-
Let's introduce the |
Beta Was this translation helpful? Give feedback.
-
I agree, allowing One option for the top-level statement problem might be to simply disallow them. It's not like anyone uses them in any realistic scenario 😀 Realiztically, we can just disallow The breaking change itself is not going to change the meaning of anyone's code apart from preventing it from compliing, unless they both used top-levels and an underscore alias, presumably vanishingly rare. |
Beta Was this translation helpful? Give feedback.
-
Nah, the new keyword should clearly be "notusing" |
Beta Was this translation helpful? Give feedback.
-
Hopefully the new breaking change warning proposal could allow us to sort this out once and for all. We should either enable: using GetSomeDisposable(); (which currently, if there are parentheses around the expression, could mean this) using _ = GetSomeDisposable(); (which currently could be a type alias) The former is actually the ideal one; it would be perfect. |
Beta Was this translation helpful? Give feedback.
-
I think both should be available, in case the local rules allow unassigned object, and do not force the discard notation for non-used return values. |
Beta Was this translation helpful? Give feedback.
-
I like your thinking. Technically it is used as you say. I prefer the
After 10 months, I've finally stopped laughing at this statement. I love top level statements: they are one of the best features added to the language in recent years. But there again I wrote my first line of code only 42 years ago, so I guess I don't yet qualify as experienced 🤣 |
Beta Was this translation helpful? Give feedback.
-
What does a "used return value" mean? |
Beta Was this translation helpful? Give feedback.
-
I mean, it is a warning (maybe from resharper), when you call a method, but do not use the return value for anything
this does not:
|
Beta Was this translation helpful? Give feedback.
-
Is it really a problem that we have an ambiguity in top-level statements? using System;
using System.Collections.Generic;
Console.WriteLine("Beginning using");
using MyResource();
Console.WriteLine("Terminating using");
IDisposable MyResource()
{
return null; // maybe something not null
} Using the block using statement will surely solve this problem. It will be clearer that the Alternatively, if we want a universal solution that also covers top-level statements, I think that indicating that this is a using statement on an expression, we could adopt something like using var MyResource();
using var x = AnotherResource(); The justification is that uninitialized variable declarations are not allowed in a scope-wide using statement. This
|
Beta Was this translation helpful? Give feedback.
-
Why not simply permit |
Beta Was this translation helpful? Give feedback.
-
Champion issue: #8606
C# 8.0 adds the possibility to declare using variables without a corresponding using block, discarding the variable as soon as the scope in which it was declared is left.
The official example shows it with assigning the using variable to an actually named variable as you would typically do:
However, this does not seem to work if I don't actually need the variable being disposed. My use case is a temporary
Seek
object which remembers the current position in a stream when created, and reverts to that position upon being disposed. It is used like this:I tried to rewrite this in C# 8.0:
However, this yields CS0118 'reader' is a variable but is used like a type. A bit stumped here already, I then tried storing the
Seek
instance in a discard variable at least:But this now yields CS0246 The type or namespace name '_' could not be found (are you missing a blahblahblah...?). More stumped, I then became even more explicit:
While this compiles, it feels very odd to me. Jokes on Visual Studio 2019 trying to break my code now too and forgetting to use
using
at all:Is this a known issue, done on purpose, or an oversight in the feature?
Beta Was this translation helpful? Give feedback.
All reactions