This one hurt my head, but I think I have got it figured out.
This confusion is caused by two quirks: the way the is
operation leaves the variable undeclared (not null), and the way the compiler optimizes away Boolean, but not bitwise, operations.
Quirk 1. If the cast fails, the variable is unassigned (not null)
Per the documentation for the new is
syntax:
If exp is true and is is used with an if statement, varname is assigned and has local scope within the if statement only.
If you read between the lines, this means that if the is
cast fails, the variable is considered unassigned. This may be counterintuitive (some might expect it to be null
instead). This means that any code within the if
block that relies on the variable will not compile if there is any chance the overall if
clause could evaluate to true
without a type match present. So for example
This compiles:
if (instance is MyClass y)
{
var x = y;
}
And this compiles:
if (true && instance is MyClass y)
{
var x = y;
}
But this does not:
void Test(bool f)
{
if (f && instance is MyClass y)
{
var x = y; //Error: Use of unassigned local variable
}
}
Quirk 2. Boolean operations are optimized away, binary ones are not
When the compiler detects a predestined Boolean result, the compiler will not emit unreachable code, and skips certain validations as a result. For example:
This compiles:
void Test(bool f)
{
object neverAssigned;
if (false && f)
{
var x = neverAssigned; //OK (never executes)
}
}
But if you use &
instead of &&
, it does not compile:
void Test(bool f)
{
object neverAssigned;
if (false & f)
{
var x = neverAssigned; //Error: Use of unassigned local variable
}
}
When the compiler sees something like true &&
it just ignores it completely. Thus
if (true && instance is MyClass y)
Is exactly the same as
if (instance is MyClass y)
But this
if (true & instance is MyClass y)
Is NOT the same. The compiler still needs to emit code that performs the &
operation and uses its output in a conditional statement. Or even if it doesn't, the current C# 7 compiler apparently performs the same validations as if it were. This may seem a little strange, but bear in mind that when you use &
instead of &&
, there is a guarantee that the &
must execute, and though it seems unimportant in this example, the general case must allow for additional complexifying factors, such as operator overloading.
How the quirks combine
In the last example, the result of the if
clause is determined at run time, not compile time. So the compiler can't be certain that y
will end up being assigned before the contents of the if
block are executed. Thus you get
if (true & instance is MyClass y)
{
var x = y; //Error: use of unassigned local variable
}
TLDR
In the situation of a compound logical operation, c# can't be sure that the overall if
condition will resolve to true if and only if the cast is successful. Absent that certainty, it can't allow access to the variable, since it might be unassigned. An exception is made when the expression can be reduced to non-compound operation at compile time, for example by removing true &&
.
Workaround
I think the way we are meant to use the new is
syntax is as a single condition with an if
clause. Adding true &&
at the beginning works because the compiler simply removes it. But anything else combined with the new syntax creates ambiguity about whether the new variable will be in an unassigned state when the code block runs, and the compiler can't allow that.
The workaround of course is to nest your conditions instead of combining them:
Won't work:
void Test(bool f)
{
if (f & instance is MyClass y)
{
var x = y; //Error: Use of unassigned local variable
}
}
Works fine:
void Test(bool f)
{
if (f)
{
if (instance is MyClass y)
{
var x = y; //Works
}
}
}