Reputation: 19285
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
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:
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.
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