Champion issue: dotnet#8606
Permit using declarations whose variable is anonymous:
void M()
{
using _ = GetDisposable();
}This statement form adds the final corner to the following square:
| New variable name | No new variable name | |
|---|---|---|
| New scope | using (var name = expr) { |
using (expr) { |
| No new scope | using var name = expr; |
using _ = expr; 🆕 |
It has been wildly popular to remove or avoid nesting by preferring the bottom form inside the "New variable" column to the top form. However, there has been no corresponding opportunity in the other column. The absence of this corner has been painfully felt. When a language user doesn't want the extra nesting, but also has no use for the variable, this results in workarounds such as creating variables named _, _1, _2, and so on.
using var _1 = factory.Lease(out var widget);
using var _2 = widget.BeginScope();Another place where this request comes up is when using ConfigureAwait for await using. The ConfigureAwait extension method on IAsyncDisposable returns a wrapper which is only good for disposal, and useless to place in a new variable. Yet users must place it in a new variable, or else they must increase nesting just because they're using ConfigureAwait.
var stream = await client.GetStreamAsync(...).ConfigureAwait(false);
await using var _3 = stream.ConfigureAwait(false);By using throwaway variable names as seen in community discussions and the Roslyn codebase itself (one example), language users are already attempting to use the language as though this feature existed.
using_statement syntax is expanded to include a new using_discard_statement syntax, 'await'? 'using' '_' '=' expression ';' (see Specification).
The lifetime of the resources acquired in a using_discard_statement, and the allowed locations for a using_discard_statement, are defined in the same way as for C# 8's using_declaration syntax.
This proposal does not create support for multiple discards in a single using statement:
❌ NOT supported:
using _ = expr1, _ = expr2;
using _ = expr1, expr2;using_declaration syntax is not expanded or unified. using_declaration is based on implicitly_typed_local_variable_declaration and explicitly_typed_local_variable_declaration which do not include simple discards. A simple discard is a special case of a declaration_expression (§12.17). (Specification work for using_declaration is ongoing at the time of writing.)
§13.14 The using statement is updated as follows.
using_statement
: 'await'? 'using' '(' resource_acquisition ')' embedded_statement
+ | using_discard_statement
;
resource_acquisition
: non_ref_local_variable_declaration
| expression
;
non_ref_local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
;
+using_discard_statement
+ : 'await'? 'using' '_' '=' expression ';'
+ ;Additions in bold:
If the form of resource_acquisition is local_variable_declaration then the type of the local_variable_declaration shall be either
dynamicor a resource type. If the form of resource_acquisition is expression, or the form of using_statement is using_discard_statement with its constituent expression, then this expression shall have a resource type. Ifawaitis present, the resource type shall implementSystem.IAsyncDisposable. Aref structtype cannot be the resource type for ausingstatement with theawaitmodifier.
Removals in strikeout, additions in bold:
Ausingstatement of the form:usingstatements of the other two forms:using («expression») «statement»and:
using _ = «expression»;
hashave the same possible formulations.
A new subsection is added:
A using discard statement has the same semantics as, and can be rewritten as, the corresponding parenthesized form of the using statement, as follows:
using _ = «expression»; // statementsis semantically equivalent to
using («expression») { // statements }and
await using _ = «expression»; // statementsis semantically equivalent to
await using («expression») { // statements }A using discard statement shall not appear directly inside a
caselabel, but, instead, may be within a block inside acaselabel.
using expr; would be a consistent extrapolation from the existing constructs, where you simply remove the curly braces and parentheses and add a semicolon. However, this statement form would conflict with using directives, particularly in top-level statements.
using _ = expr; also conflicts with using alias directives, but _ has a strong sense of "discard" and is discouraged as an identifier name. (There is an open question on this point.)
In most cases, the expression in a using statement will not be of the form A.B. It is more common to call a constructor or method. In these common scenarios, there would be very little visual confusion:
using someLock.BeginScope();
using new SomeResource(...);The compiler could disambiguate by checking whether the expression binds to a namespace or type versus to some other kind of expression. The human reader would only be confused if this feature was used in a misleading way. Even when the underlying syntax is the same, as in the following deliberately-contrived example, it is obvious from context that the first using below involves a namespace or type, and that the second using does not:
using System.ComponentModel;
var result = TryXyz();
using result.Value;If this syntax was available, users could also write using _ = expr; as well in scenarios where they felt it improved clarity. It would work the same way as using (_ = expr) { does today. This option optimizes for the user's flexibility in expressiveness. Community feedback is already beginning to show a desire for this flexibility.
Shall we proceed to design the simpler form, allowing using new SomeResource(); and getting using _ = new SomeResource(); as a corollary, or shall we design the form that stands out visually with _ = as the only form?
This open question assumes the using expr; syntax.
If the top-level expression is a parenthesized expression, this syntax would conflict with using (expr); which compiles successfully with a warning about an empty statement. Shall we disallow the expression to be a parenthesized expression?
If the top-level expression is a simple assignment expression, this syntax would conflict in top-level statements with using alias directives: using existingVariable = expr;. Shall we disallow the expression to be a simple assignment expression, or disambiguate based on whether expr is a type or namespace?
This open question assumes the using _ = expr; syntax.
A legal program may exist as follows:
using _ = SomeNamespace;
var xyz = new _.SomeType();Disallowing the use of _ as an identifier has been previously discussed. Is there appetite for a tiny subset of this breaking change, disallowing _ as an identifier only for using aliases and only in files with top-level statements?
Other alternatives:
- Disallow usings with discards in top-level statements
- Only permit usings with discards in top-level statements when nested inside a block statement
- Only permit usings with discards in top-level statements when following some other top-level statement
- Always allow both forms, and prefer the existing meaning if the expression binds to a namespace or type, and prefer the new meaning if the expression binds to a value.