Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Null propagation operator, out parameters and false compiler errors?

Let's assume I have a class that has a property of type Dictionary<string,string>, that may be null.

This compiles but the call to TryGetValue() could throw at a NullRef exception at runtime:

MyClass c = ...;
string val;
if(c.PossiblyNullDictionary.TryGetValue("someKey", out val)) {
    Console.WriteLine(val);
}

So I'm adding a null-propagating operator to guard against nulls, but this doesn't compile:

MyClass c = ...;
string val;
if( c.PossiblyNullDictionary ?. TryGetValue("someKey", out val) ?? false ) {

    Console.WriteLine(val); // use of unassigned local variable

}

Is there an actual use case where val will be uninitialized inside the if block, or can the compiler simply not infer this (and why) ?

Update: The cleanest (?) way to workaround^H^H^H^H^H fix this is:

MyClass c = ...;
string val = null; //POW! initialized.
if( c.PossiblyNullDictionary ?. TryGetValue("someKey", out val) ?? false ) {

    Console.WriteLine(val); // no more compiler error

}
like image 337
Cristian Diaconescu Avatar asked Dec 06 '17 22:12

Cristian Diaconescu


2 Answers

Seems like you have run into a limitation of the compilers understanding of ?. and ?? which isn't too surprising, given that they aren't really fully incorporated in the language.

If you make your test explicit without the newer operators, the compiler will agree with you:

MyClass c = new MyClass();
string val;
if (c.PossiblyNullDictionary != null && c.PossiblyNullDictionary.TryGetValue("someKey", out val)) {
    Console.WriteLine(val); // now okay
}
like image 53
NetMage Avatar answered Nov 14 '22 14:11

NetMage


By initializing val to a erhm, value (e.g., String.Empty) the compiler is able to grok the intent for the null operators and behaves as expected (via LINQPad, natch):

void Main()
{
    MyClass c = new MyClass();
    string val = string.Empty;
    if (c.PossiblyNullDictionary?.TryGetValue("someKey", out val) ?? false)
    {

        Console.WriteLine(val);

    }
}
public class MyClass {
    public Dictionary<string, string> PossiblyNullDictionary;
}
// Define other methods and classes here

Ed: by 'grok the intent' I meant that the compiler can't make important guarantees about the program's characteristics if it allows execution to leave the current scope with val uninitialized. When it evaluates the null operators, the method invocation.

The use case you ask for is this: Say that instead of TryGetValue, we have bool SomeMethod(string s, out v). Let's say that when invoked, SomeMethod is naughty and simply has a body of return true;. The compiler treats method invocation bodies as opaque (since it may not always be in an assembly available/visible to the compiler), so it concludes that there's no way to prove that val is ever initialized.

ed: In response to some comments, I wanted to update my answer to point out that this behavior isn't specific to the ?? or ?. C# language features; you can reproduce the same effect simply by using a ternary expression:

c.PossiblyNullDictionary == null ? 
    false : 
    c.PossiblyNullDictionary.TryGetValue("someKey", out val) 
 //error: use of possibly uninitialized local variable
like image 45
Josh E Avatar answered Nov 14 '22 14:11

Josh E