yww325
yww325

Reputation: 305

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.

Upvotes: 12

Views: 9829

Answers (4)

JuanR
JuanR

Reputation: 7783

The null-conditional operator introduces a different execution path where it short-circuits TryGetValue. However, the value variable exists anyways since it is scoped outside of the if and TryGetValue statements, which would indicate this flavor of syntactic sugar must result in the good old declaration of the variable prior to its use. For instance:

if (myDict?.TryGetValue("hello", out var value) == true)
{
    Console.WriteLine("Hello" + value.ToString());
}        
//Current issue aside, this is legal as the variable exists at a higher scope, 
//beyond the if and TryGetValue statements although the brackets give the illusion of scope.
value = 2;

Should be equivalent to:

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

Which also results in the compilation error.

However, the same error would be issued for myDict if it was null and you didn't use the null-conditional operator:

Dictionary<string, int> myDict; //Not initialized        
//You would get the warning for myDict here instead
if (myDict.TryGetValue("hello", out var value) == true)
{
    Console.WriteLine("Hello" + value.ToString());
}

In other words, myDict can never be null and compile without the same error so there is no need to use a null-conditional operator. This is why removing it fixes the issue.

That being said, the documentation states:

No need to assign an initial value. By declaring the out variable where it's used in a method call, you can't accidentally use it before it is assigned.

So, in theory you are accidentally using the variable before it's assigned because the language constructs allow it. However, the compiler is identifying an issue at a lower level and not letting you go through with it.

In my mind, it's valid compiler verification obscured by syntactic sugar fog.

Upvotes: 0

Stefan
Stefan

Reputation: 17658

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.

Upvotes: 10

J...
J...

Reputation: 31403

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.

Upvotes: 3

patrickSmith
patrickSmith

Reputation: 254

I would suggest initializing the value variable before your "TryGetValue" call.

Upvotes: 0

Related Questions