- Scope of while condition expression variables
- Mixed deconstruction
- Unused expression variables
- Declarations in embedded statements
- Not-null pattern
We are compelled by the argument in #15529 about the scope of expression variables occurring in while
conditions.
while (src.TryGetNext(out int x))
{
// Same or different `x` each time around?
}
// x in scope here?
More broadly we seem to have the following options for scopes and lifetimes of such declarations:
- There is a single variable
x
for the wholewhile
statement, which is initialized repeatedly, each time around. It is in scope outside thewhile
statement. - There is a single variable
x
for the wholewhile
statement, which is initialized repeatedly, each time around. It is only in scope inside thewhile
statement. - There is a fresh variable
x
for each iteration of thewhile
statement, initialized in the condition. It is only in scope inside thewhile
statement.
There is a clear argument for the lifetime of the variable being a single iteration. We know from foreach
that this leads to better capture in lambdas, but perhaps more importantly it avoids the odd behavior of the same variable getting initialized multiple times. (Pretty much the only way to otherwise achieve that in C# is through insidious use of goto
).
This puts us squarely in option 3 above, where it is meaningless to put "the" variable in scope outside of a while loop. Indeed, from the outside there is no notion of "the" variable: there will be multiple x
es over the execution of the loop.
In light of a changed while
loop we also need to examine what that means to the for
loop, which, while not defined as such in the spec, can be informally understood as "desugaring" into a while
loop.
for (<decl>; <cond>; <incr>) <body>
==>
{
<decl>
while(<cond>)
{
<body>
cont:
{ <incr> }
}
}
So with respect to scopes and lifetimes, <cond>
should behave the same as the condition in a while loop.
Similarly, <incr>
should be a fresh variable each time around. Furthermore it should occur in its own little nested scope, since variables introduced in it won't be definitely assigned until the end of each iteration.
We want to change to the narrow scopes and short lifetime for expression variables introduced in the condition of while and for loops, and in the increment of for loops.
This means that the if
statement becomes more of a special case, with expression variables in its condition having broad scope. That is probably a good place to land. The main scenarios for this behavior are driven by the if statement to begin with, and it is also the one kind of statement where the lifetime and definite assignment situation for such variables makes it reasonable for them to persist outside of the statement.
The reinterpretation of deconstruction in terms of declaration expressions would let us generalize so that:
- Would allow mixing existing and newly declared variables in a deconstruction
- Would allow newly declared variables in deconstruction nested in expressions
- No error when occuring as an embedded statement
From a language design point of view we are confident in this generalization of the deconstruction feature. It is doubtful whether the change can make it into C# 7.0 at this point. Even though it is a small code change in and of itself, it introduces testing work and general churn.
Should we warn when an expression variable goes unused? Typically such variables would be dummy variables, and with discards _
you no longer need them.
On the other hand, we could leave it to the IDE to suggest discards etc, rather than give a warning from the language.
Let's not add the warning. Keep the warning in place where it already is in existing C#.
C# currently has a grammatically enforced restriction against declaring "useless" variables as "embedded statements":
if (x > 3) var y = x; // Error: declaration statement not allowed as an embedded statement
Of course expression variables now give you new ways of similarly declaring variables that are immediately out of scope and hence "useless". Should we give errors for those new situations also?
Let's not add errors for deconstruction situations. In principle we probably want to remove even the existing limitation, but it's work we won't push for in C# 7.0.
It's been suggested to have a pattern for testing non-nullness (just as there is a null
pattern for checking nullness).
No time in C# 7.0 but a good idea.