Параметр объединения с нулевым значением дает неожиданное предупреждение

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

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

Я получаю сообщение об ошибке CS0165 use of unassigned local variable 'value', чего я не ожидал. Как может value быть неопределенным? Если словарь нулевой, внутренний оператор вернет false, что заставит внешний оператор оцениваться как ложный, возвращая Default.

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


person Jakob Möllås    schedule 17.03.2019    source источник
comment
Специальные состояния «Определенно назначено после истинного выражения» или «Определенно назначено после ложного выражения» отслеживаются только для ограниченного числа операторов. Насколько я понимаю, ?. и ?? среди них нет. Вместо этого вы можете использовать (dict != null && dict.TryGetValue(1, out var value)) ? value : "Default".   -  person user4003407    schedule 17.03.2019
comment
Да, это то, что я сделал, я также создал метод расширения для упрощения. Было бы неплохо иметь возможность использовать такие конструкции, например, в синтаксисе Linq Query без дополнительных методов расширения.   -  person Jakob Möllås    schedule 17.03.2019


Ответы (2)


Ваш анализ правильный. Это не анализ, который делает компилятор, потому что компилятор выполняет анализ, требуемый спецификацией 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");

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

person Eric Lippert    schedule 17.03.2019
comment
Согласитесь, ваше решение намного читабельнее. Я не уверен, что хочу скрыть проверку null на сайте вызова, но в любом случае проверка на null в методе расширения не повредит. Вопрос, однако, вы хотели сказать, что условие в этом случае не является константой во второй пуле? - person Jakob Möllås; 18.03.2019

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

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

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

person Henk Holterman    schedule 17.03.2019
comment
Но все выходные параметры должны быть назначены перед возвратом из метода, за исключением случаев, когда метод выдает исключение, верно? Таким образом, компилятор должен знать, что значение out по крайней мере имеет значение по умолчанию, которое я ожидал. Если бы код сказал ?? true, это привело бы к неназначенному значению. - person Jakob Möllås; 17.03.2019
comment
Да, я думаю, что @PetSerAl ближе, если не учитывать операторы ?. и ??. - person Henk Holterman; 17.03.2019