C# Nullability не выводится правильно

Рассматривать:

#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);
    }
  }
}

Затем я пытаюсь:

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

Я получаю предупреждение:

Предупреждение CS8602 Разыменование возможно нулевой ссылки.

Почему? Я ожидал, что после проверки !ok компилятор выведет, что john не ноль

Еще я попробовал:

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

(удалено ? из Диспетчера результат и заменен (false, null) с участием default)
Теперь я не получаю предупреждения, но я также не получаю предупреждения, если снимаю чек для !ok.

Есть ли способ достичь того, что я хочу здесь - предупреждение, если и только если не было предыдущей проверки для !ok (то есть я забыл это проверить)

благодаря

1 ответ

Решение

Почему? Я ожидал, что после проверки на! Ok компилятор определит, что john не равен нулю

Это не работает по двум причинам:

  1. Анализ допустимости NULL рассматривает только один метод за раз.

При анализе:

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

компилятор не видит этот метод:

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

и скажи это value не равно нулю, когда ok правда.

  1. Анализ допустимости пустых значений не отслеживает логические переменные.

На данный момент компилятор недостаточно умен, чтобы отслеживать, откуда булевы переменные, и обновлять на их основе допустимость значений NULL. Например, следующее не предупреждает:

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);
Другие вопросы по тегам