meetings/2018/LDM-2018-07-09.md
QOTD: "Yeah, it's easy if you do it in a shi**y way"
using var feature
using expr; grammar formPattern-based Dispose in the using statement
Relax Multiline interpolated string syntax ($@)
using var FeatureMotivation
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.
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 formThese 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 for using var _ = expr;
- Or using _ = 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.
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
using statementWe 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 allow params or optional parametersDo 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.
It's hard to remember which is the correct syntax: $@"" or @$"". The
proposal is to allow either.
Conclusion
No objections.