Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error combining 'if' statements that null-checks and Pattern Matches

The following works as expected:

dynamic foo = GetFoo();

if (foo != null)
{
    if (foo is Foo i)
    {
        Console.WriteLine(i.Bar);
    }
}

but if I combine the if statements like so:

if (foo != null && foo is Foo i)
{
    Console.WriteLine(i.Bar);
}

then I receive a compiler warning

Use of unassigned local variable 'i'

Can anyone explain why this happens?

like image 740
Chris Avatar asked May 16 '18 09:05

Chris


2 Answers

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.

like image 171
Uładzisłaŭ Avatar answered Oct 18 '22 21:10

Uładzisłaŭ


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...)

like image 8
Matthew Watson Avatar answered Oct 18 '22 21:10

Matthew Watson