Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I put the code below, and also uploaded to a online c# compiler: jdoodle.com/a/1jww the code can compile and run online, however, it doesn't compile in my local visual studio.

I am using:

Visual Studio 2017 15.9.13,

Console app, .Net Framework 4.7.2

Language version c# 7.3

Microsoft (R) Visual C# Compiler version 2.10.0.0 (b9fb1610)

Below is the code:

class Program
{
    static void Main()
    {
        Dictionary<string,int> myDict = new Dictionary<string,int>();
        myDict.Add("hello", 1);

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

expecting to see Hello1 in Console.Output Because if the condition is true, the Null-Conditional check must have returned a non-null value, and the key is existing in the dictionary and the value must have been assigned when returning from the TryGetValue method.

Because according to the documentation:

the called method is required to assign a value before the method returns.


update: This is an open issue in https://github.com/dotnet/roslyn/issues/32572
I think it's an true issue/bug if the requirement for compiler includes not to give false alarm. My arguments:
Whenever CPU executing to the point inside the if bracket code block, the value must have returned from TryGetValue call and is NOT "unassigned local variable". An easier solution is to give something like "unable to determine assignment status" as warning instead of error, if compiler can't look forward for assignment status when interpreting null conditional operator.

like image 798
yww325 Avatar asked Jun 26 '19 19:06

yww325


Video Answer


2 Answers

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.

like image 58
Stefan Avatar answered Nov 11 '22 10:11

Stefan


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.

like image 37
J... Avatar answered Nov 11 '22 09:11

J...