8.3 KiB
LDM July 9th, 2018
QOTD: "Yeah, it's easy if you do it in a shi**y way"
Agenda
-
using var
feature- Overview
- Tuple deconstruction grammar form
using expr;
grammar form- Flow control safety
-
Pattern-based Dispose in the
using
statement -
Relax Multiline interpolated string syntax (
$@
)
using var
Feature
Motivation
Proposal: https://github.com/dotnet/csharplang/pull/1703
It's a common problem that multiple using
statements can require successive
nesting, causing what is mostly linear code to have the "down and to the
right" problem, where increasing indentation makes the code less readable,
not more. One way people try to solve this is using the
{
using (expr1)
using (expr2)
using (expr3)
{ ... }
}
syntax, but that has two problems. First, many style guidelines prohibit
"braceless" usings, but make an exception for this specific case. Second, if
there is any intervening code required between the using
expressions, this
syntax form is not allowed.
Objections
Objections to this feature fall mainly in two categories. Either there is worry about determinism and ordering, or that this feature isn't sufficiently general to encompass the scenarios we would consider making the feature "worth it."
The determinism concern is that refactoring from the using (...) {...}
form
could unintentionally lengthen the liveness scope to the entire method,
instead of just to the closing brace of the using. The ordering concern is
that nesting provides very clear ordering semantics, and the "stacked using"
form also has a clear ordering, since there cannot be any code in between
each using
. This isn't necessarily true for using-variables. It's possible
that both of these concerns could be mitigated by better refactoring and
analysis tools.
The generality concern is mainly around the using (expr) { ... }
statement
form, which doesn't have an equivalent using-variable form in the current
design.
Conclusion
It's worth it. The concerns are valid, but don't seem bad enough to block the feature.
Tuple deconstruction grammar form
The first question was about the proposed grammar. The current design is a
new type of statement (local-using-declaration
). There are two potential
holes in the grammar: no space for tuple deconstructions and no using expr;
form.
For deconstruction, we came up with a number of potential forms:
(using var x, using var y) = M(); // Form 0
using (x, y) = M(); // Form 1
using (var x, var y) = M(); // Form 2
using var (x, y) = M(); // Form 3
using var t = M(); // Form 4
Of these, only (4) would be legal in the current proposal. Of the remaining forms, form (0) seemed the clearest. There was consensus that this implied the declaration of two new variables, each of which was independently disposed, in the style of
using var x = M1(), y = M2();
It was not immediately clear whether the tuple itself was disposed in form (0). This was a common complaint with the rest of the forms as well: it is unclear what the semantics of each statement is. Is the tuple itself being disposed? Is disposal distributed over the elements? Both? Some tuple deconstructions also happen in "reverse" order of the tuple elements' lexical ordering. If dispose is distributed, what order are the elements disposed in?
This also raised the question of nested declarations in the initializer, e.g.
using var x = M1(out var y)
Is x
the only using
variable? Or is y
one as well?
Conclusion
Let's continue with the proposal as-is. Form (4) works and should work. There
may be compelling scenarios to open up the syntax to tuple deconstructions,
but we don't have a convincing argument yet. We also don't have a clear rule
for prohibition. For nested declarations, they are not declared as using
variables.
using expr
grammar form
These concerns dovetailed into discussion of the using expr;
form, because some of these grammar
forms may compose. For example, since var (x, y)
is an expression in C#, the following could be
a potentially legal statement with no modification:
using var (x, y) = M();
In this case var (x, y) = M()
would be the expr
in using expr;
.
This form seems desirable to round out the feature, but it isn't clear how it fits into the language.
The previous decision seems to imply we don't want using
deconstructions, but it isn't clear what
rule we would use to prohibit them, in a principled sense. The feature also has some integration
concerns. using (expr);
is already a legal construct in the C# language with different semantics,
although the compiler gives a warning about it today. There is some concern that using expr;
and
using (expr);
are too close grammatically and that the syntax effectively rules out parenthesized expressions.
Finally, there were questions about grammatical ambiguity with possible
future language features. If C# were to allow statements on the top level, a
using System;
line could either be a using-directive if System
is a
namespace, or a using-statement if System
is a type. The same problem could
occur if we were to allow using-directives at the statement level. This
doesn't seem very bad since we already have similar ambiguities with Color Color
rules and resolve them properly during semantic analysis. These
ambiguities are also probably present for using-directive aliases.
There were a couple proposals to try to deal with some of these problems:
-
Any expression that declares variables is disallowed as a
using expr;
-
Hold off on
using expr;
for now. -
Allow
_
as a discard forusing var _ = expr;
- Orusing _ = expr;
Conclusion
This is a blocking issue that we must decide on for C# 8.0. Either we should disallow this form entirely or find some principle to use to reject the constructions we find confusing. However, we think this problem is solvable and shouldn't block continued work on the feature.
Flow control safety
The last design issue was safety in the presence of goto
and similar flow
control features (e.g., local functions). The existing spec notes that backward
flow control is not a problem, but what about forward flow control? For example,
{
goto target;
using var x = new FileStream(...);
target:
var y = x;
return;
}
In the previous example, this is an error, because x
is not definitely assigned.
In fact, all uses in this category, where flow is manipulated to skip over the
variable definition before a read, are safe because the variable will not be
definitely assigned. In addition, because using
variables are read-only, it also
cannot be assigned later.
One case which the spec does not currently handle is
{
goto target;
using var x = new FileStream(...);
target:
return;
}
Here x
is never read, so there would be no definite assignment errors. However,
there is an implicit read of the variable at the end of the variable lifetime, which
could be a read of an unassigned variable.
Conclusion
A new line to the spec should be added saying that, if the end of a using
variable's
lifetime is reachable, that variable must be definitely assigned at that point.
Open question
- This needs more precise language. The spec does not have reachability at "points". What do we mean when we say the end of a block is "reachable"?
Pattern-based using
statement
We like the feature. Main question: what type of pattern do we look for? As a general guideline, we don't want to have another special case pattern. However, it seems like we have multiple styles already.
GetAwaiter
doesn't allowparams
or optional parameters- LINQ does
Do we want using
to be like await
or like LINQ?
Also, do we require void
return type? Most of the patterns today have
strict requirements on return type, but they also usually consume the return
type. using
does not.
Conclusion
Keep the spec as is: Dispose
must be parameter-less in instance-form,
void
-returning, and accessible. This allows for extension methods, but
not optional parameters or params
.
Multiline interpolated string syntax
It's hard to remember which is the correct syntax: $@""
or @$""
. The
proposal is to allow either.
Conclusion
No objections.