The following works as expected:
dynamic foo = GetFoo();
if (foo != null)
{
if (foo is Foo i)
{
Console.WriteLine(i.Bar);
}
}
Ok, I was thinking for some time about this issue, and it looks that compiler behavior is pretty damn correct and it's not so hard to reproduce it even without dynamic
values.
Some strange code will be required, nevertheless.
To start with we will overload &&
operator for our Foo
type. It's not possible to overload short circuit logical operators directly, so we will overload true
, false
and &
separately.
public static bool operator true(Foo x) => true;
public static bool operator false(Foo x) => true;
public static Foo operator &(Foo foo, Foo val) => new Foo();
Initially we had an expression foo != null && foo is Foo i
in if
block, now we want &&
from it to bound to our overload. For this reason we will overload !=
operator and ==
as well as they should be paired always.
public static Foo operator !=(Foo val, Foo val2) => new Foo();
public static Foo operator ==(Foo val, Foo val2) => new Foo();
For now foo != null
evaluates to Foo
and foo is Foo
evaluates to bool
, but our &&
overload has signature (Foo, Foo)
— still a mismatch, will add one more overload for implicit conversion from bool
.
public static implicit operator Foo(bool val) => new Foo();
Here is the code for Foo
type we've got so far
class Foo
{
public static bool operator true(Foo x) => true;
public static bool operator false(Foo x) => true;
public static Foo operator &(Foo foo, Foo val) => new Foo();
public static implicit operator Foo(bool val) => new Foo();
public static Foo operator !=(Foo val, Foo val2) => new Foo();
public static Foo operator ==(Foo val, Foo val2) => new Foo();
}
And voila! We have the same error for this piece.
static void Main(string[] args)
{
Foo foo = GetFoo();
if (foo != null && foo is Foo i)
{
// Use of unassigned local variable i
// Local variable 'i' might not be initialized before accessing
Console.WriteLine(i);
}
}
static Foo GetFoo() => new Foo();
And indeed, if we for example use foo is string i
instead of foo is Foo i
, i
won't be initialized at runtime, but we will be inside if
block.
Initial issue is rather equivalent, because of dynamic
values involved. foo != null
is dynamic
, as far as foo
is dynamic
, so it means &&
should be bound at runtime, and we have no guarantees that i
will be initialized.
Looks like Matthew quoted the same thing from github issue, but I personally wasn't able to grasp it from the beginning.
It would appear that this is not, in fact, a compiler error.
It was previously reported as a bug here.
However, it has been closed as not a bug. The reason is because of this part of the C# language spec (note: I am quoting here from user gafter
on GitHub - this is NOT original content from myself):
If an operand of a conditional logical operator has the compile-time type dynamic, then the expression is dynamically bound (Dynamic binding). In this case the compile-time type of the expression is dynamic, and the resolution described below will take place at run-time using the run-time type of those operands that have the compile-time type dynamic.
Specifically, the && operation is not a compile-time boolean short-circuiting operation because its right-hand operand is of type dynamic.
Subtle stuff, and as DavidG says above, another reason to avoid dynamic
where possible! (And I must confess, I'm still not completely convinced it's not a bug, but that's just me not understanding everything I guess...)