kofifus
kofifus

Reputation: 19285

C# Nullability not deduced correctly

Consider:

#nullable enable

class Manager { 
  public int Age; 
}

class Archive {
  readonly Dictionary<string, Manager> Dict = new Dictionary<string, Manager>();

  public (bool ok, Manager? value) this[string key] {
    get {
      return Dict.TryGetValue(key, out var value) ? (true, value) : (false, null);
    }
  }
}

I then try:

Archive archive = new Archive();
var (ok, john) = archive["John"];
if (!ok) return;
int age = john.Age; // <-- warning

I get a warning:

Warning CS8602 Dereference of a possibly null reference.

Why ? I expected that after checking for !ok the compiler will deduce that john is not null

Another thing I tried was:

public (bool ok, Manager value) this[string key] {
  get {
    return Dict.TryGetValue(key, out var value) ? (true, value) : default;
  }
}

(removed ? from Manager result and replaced (false, null) with default)
I now get no warning, but I also get no warning if I remove the check for !ok.

Is there any way to achieve what I want here - a warning if and only if there was no previous check for !ok (that is I forgot to check for it)

Thanks

Upvotes: 2

Views: 326

Answers (1)

Yair Halberstadt
Yair Halberstadt

Reputation: 6821

Why ? I expected that after checking for !ok the compiler will deduce that john is not null

There's two reasons why this doesn't work:

  1. Nullability analysis only looks at one method at a time.

When analyzing:

Archive archive = new Archive();
var (ok, john) = archive["John"];
if (!ok) return;
int age = john.Age; // <-- warning

the compiler can't see this method:

  public (bool ok, Manager? value) this[string key] {
    get {
      return Dict.TryGetValue(key, out var value) ? (true, value) : (false, null);
    }
  }

and tell that value is not null when ok is true.

  1. Nullability analysis doesn't track boolean variables.

At the moment the compiler isn't smart enough to track where boolean variables come from, and update nullability based on them. For example the following doesn't warn:

M(string? str)
{
    if (string != null)
        Console.WriteLine(str.Length);
}

But the following equivalent code does:

M(string? str)
{
    var isNotNull = string != null;
    if (isNotNull)
        Console.WriteLine(str.Length);
}

Is there any way to achieve what I want here - a warning if and only if there was no previous check for !ok (that is I forgot to check for it)

Not with tuples I'm afraid. The best way is using out parameters, although it will mean you can't use an indexer:

public bool TryGetManager(string key, [NotNullWhen(true)] Manager? manager) 
    => Dict.TryGetValue(key, out manager);

Upvotes: 2

Related Questions