Нулевой объединяющий параметр дает неожиданное предупреждение - PullRequest
6 голосов
/ 17 марта 2019

Используя эту конструкцию:

var dict = new Dictionary<int, string>();
var result = (dict?.TryGetValue(1, out var value) ?? false) ? value : "Default";

Я получаю сообщение об ошибке "1004 *", что не соответствует ожиданиям.Как может value быть неопределенным?Если словарь равен нулю, внутренний оператор вернет false, что сделает внешний оператор оценочным как ложное, вернув Default.

Чего мне здесь не хватает?Это просто компилятор не может полностью оценить оператор?Или я как-то все испортил?

Ответы [ 2 ]

10 голосов
/ 17 марта 2019

Ваш анализ верен. Это не анализ, который выполняет компилятор, потому что компилятор выполняет анализ, который требуется спецификацией C #. Этот анализ выглядит следующим образом:

  • Если условие выражения condition?consequence:alternative является константой времени компиляции true, то альтернативная ветвь недоступна; если false, то ветвь следствия недостижима; в противном случае обе ветви достижимы.

  • Условие в этом случае не является константой, поэтому и следствие, и альтернатива достижимы.

  • локальная переменная value определенно присваивается только в том случае, если dict не равно нулю, и, следовательно, value - это , не определенно назначается при достижении следствия.

  • Но следствие требует, чтобы value был определенно назначен

  • Так что это ошибка.

Компилятор не такой умный, как вы, но это точная реализация спецификации C #. (Обратите внимание, что я не набросал здесь дополнительные специальные правила для этой ситуации, которые включают в себя предикаты, такие как «определенно назначенные после истинного выражения» и т. Д. Для получения подробной информации см. Спецификацию C #.)

Кстати, компилятор C # 2.0 был слишком умным. Например, если бы у вас было условие типа 0 * x == 0 для некоторого int local x, это вывело бы «это условие всегда истинно независимо от значения x» и пометило бы альтернативную ветвь как недостижимую. Этот анализ был правильным в том смысле, что он соответствовал реальному миру, но он был неверным в том смысле, что спецификация C # ясно говорит, что вычет должен быть сделан только для констант времени компиляции, и в равной степени ясно, что выражения, включающие переменные не являются постоянными .

Помните, цель этой вещи - найти ошибки , и что более вероятно? Кто-то написал 0 * x == 0 ? foo : bar, намереваясь, чтобы оно имело значение «всегда foo», или что они написали ошибку случайно? Я исправил ошибку в компиляторе, и с тех пор он строго соответствует спецификации.

В вашем случае ошибки нет, но код слишком сложен для анализа компилятором, поэтому, вероятно, он слишком сложен, чтобы ожидать анализа от людей. Посмотрите, можете ли вы упростить это. Что я могу сделать, это:

public static V GetValueOrDefault<K, V>(
  this Dictionary<K, V> d,
  K key,
  V defaultValue)
{
    if (d != null && d.TryGetValue(key, out var value))
      return value;
    return defaultValue;
}
…
var result = dict.GetValueOrDefault(1, "Default");

Цель должна состоять в том, чтобы сделать сайт вызова читабельным; Я думаю, что мой сайт звонков значительно лучше читается, чем ваш.

4 голосов
/ 17 марта 2019

Разве компилятор не может полностью оценить оператор?

Да, более или менее.

Компилятор не отслеживает неназначенные, он отслеживает противоположные «окончательно назначенные».Он должен где-то остановиться, в этом случае ему нужно будет включить знания о библиотечном методе TryGetValue().Это не так.

...