Why compiler throw error CS0165: Use of unassigned local variable?

It's due to the compiler difference.

In this fiddle, https://dotnetfiddle.net/5GgGNS, you can see the error, which is omitted in the mono compiler.

I think the error is valid due to the fact that this line

if (myDict?.TryGetValue("hello", out var value) == true)

is not guaranteed to initialize the local variable value.

If you would rewrite it to:

if (myDict?.TryGetValue("hello", out var value) == null)

it would try to access value.

Now, the null value, or true in your case, could be a function's return value, which would only be known at run time.

But, since all variables are basically always initialized, it's just a compiler feature.

On the other hand, according to the C#5 specs:

A local variable introduced by a local-variable-declaration is not automatically initialized and thus has no default value. For the purpose of definite assignment checking, a local variable introduced by a local-variable-declaration is considered initially unassigned. A local-variable-declaration may include a local-variable-initializer, in which case the variable is considered definitely assigned only after the initializing expression (§5.3.3.4).

But your code is C# 6.

So my conclusion is that the compilers interpret it differently. The Microsoft compiler takes the ?. operator into account. You should file it as a bug, or finding at least, maybe even at both parties.


Argumentation

Fun fact, if you use this code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

class Program
{
    static void Main()
    {
        //Your code goes here
        Dictionary<string,int> myDict = null;

        if (myDict?.TryGetValue("hello", out var value) == null)
        {               
            Console.WriteLine("Hello" + value.ToString());
        }
    }
}

[using https://www.jdoodle.com/compile-c-sharp-online , mono 5.10.1]

You'll see the actual initialization to default(T) at work. The output is Hello0. Nevertheless, it's remarkable because due to the ?, and the fact that myDict is null, TryGetValue shouldn't be called and leaving value "uninitialized".

The null-conditional operators are short-circuiting. That is, if one operation in a chain of conditional member or element access operations returns null, the rest of the chain doesn't execute.

source

But..., since there are no uninitialized variables; if it compiles, the compiler will make sure it's behavior is not undefined.


So, since value is initialized, on run-time, the question remains if it's a valid compiler error at build time. Regarding to the run-time intend of the code it is (and that's why the error was there in the first place), but I think it remains a grey area.

Do note that according to this default(T) is not override-able, which would actually lead to no condition where it fails.


By running this little test:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

class Program
{
    static void Main()
    {
        //Your code goes here
        Dictionary<string,int> myDict = null;

        if (myDict?.Bar(out var test) == null)
        {               
            Console.WriteLine("does hit");
        }
    }
}

static class Foo
{
    public static object Bar(this Dictionary<string,int> input, out int test)
    {
        test = 3;
        Console.WriteLine("does not hit");
        return 1;
    }
}

[using https://www.jdoodle.com/compile-c-sharp-online , mono 5.10.1]

The output becomes:

does hit

And you can verify the correct run-time behavior of the ?. operator.


The null conditional ?. removes the guarantee that value will be assigned to since TryGetValue will only be called conditionally if myDict is not null.

You enforce the assignment of value inside the if statement afterwards with == true since the left side will return null if TryGetValue is not called due to myDict itself being null. The compiler, however, cannot make this inference leap in the general case so you have to help it out either by testing myDict for null beforehand (and skipping ?.) or initializing value beforehand.