Roslyn не удалось скомпилировать код

После переноса моего проекта с VS2013 на VS2015 проект больше не собирается. Ошибка компиляции возникает в следующем операторе LINQ:

static void Main(string[] args)
{
    decimal a, b;
    IEnumerable<dynamic> array = new string[] { "10", "20", "30" };
    var result = (from v in array
                  where decimal.TryParse(v, out a) && decimal.TryParse("15", out b) && a <= b // Error here
                  orderby decimal.Parse(v)
                  select v).ToArray();
}

Компилятор возвращает ошибку:

Ошибка CS0165 Использование неназначенной локальной переменной 'b'

В чем причина этой проблемы? Можно ли исправить это через настройку компилятора?

Ответ 1

Что вызывает эту проблему?

Похож на ошибку компилятора. По крайней мере, так оно и было. Хотя выражения decimal.TryParse(v, out a) и decimal.TryParse(v, out b) оцениваются динамически, я ожидал, что компилятор все равно поймет, что к моменту достижения значения a <= b обе команды a и b определенно назначены. Даже с странностями, которые вы можете придумать при динамическом наборе текста, я ожидал, что когда-нибудь оцените a <= b только оценку обоих вызовов TryParse.

Однако, оказывается, что с помощью оператора и преобразования сложно реализовать выражение A && B && C, которое оценивает a и C, но не b - если вы достаточно хитры. См. отчет об ошибке Roslyn для изобретательного примера Neal Gafter.

Сделать работу с dynamic еще сложнее - семантика, связанная с динамическими операндами, сложнее описать, потому что для выполнения разрешения перегрузки вам необходимо оценить операнды, чтобы узнать, какие типы задействованы, что может быть контр-интуитивным. Однако снова Neal придумал пример, который показывает, что требуется ошибка компилятора... это не ошибка, это исправление ошибки. Огромное количество славы Нилу за его доказательство.

Можно ли исправить это через настройки компилятора?

Нет, но есть альтернативы, которые позволяют избежать ошибки.

Во-первых, вы можете остановить его от динамического - если вы знаете, что будете использовать только строки, то вы можете использовать IEnumerable<string> или дать переменной диапазона v тип string (т.е. ). Это был бы мой предпочтительный вариант.

Если вам действительно нужно сохранить динамику, просто дайте b значение для начала:

decimal a, b = 0m;

Это не повредит - мы знаем, что на самом деле ваша динамическая оценка не сделает ничего сумасшедшего, поэтому вы все равно в конечном итоге присвоите значение b, прежде чем использовать его, сделав исходное значение несущественным.

Кроме того, кажется, что добавление круглых скобок также работает:

where decimal.TryParse(v, out a) && (decimal.TryParse("15", out b) && a <= b)

Это изменяет точку, в которой срабатывают различные куски разрешения перегрузки, и случается, чтобы компилятор был счастлив.

Остается одна проблема - правила спецификации для определенного назначения с оператором && необходимо уточнить, чтобы заявить, что они применяются только тогда, когда оператор && используется в своей "обычной" реализации с двумя bool операнды. Я попытаюсь убедиться, что это исправлено для следующего стандарта ECMA.

Ответ 2

Это похоже на ошибку или, по крайней мере, регрессию в компиляторе Roslyn. Для отслеживания этого файла была отправлена ​​следующая ошибка:

https://github.com/dotnet/roslyn/issues/4509

Тем временем, у Jon отличный ответ есть пара рабочих мест.

Ответ 3

С тех пор, как я так сильно участвовал в отчете об ошибке, я попытаюсь объяснить это сам.


Представьте, что T - это определенный пользовательский тип с неявным преобразованием в bool, который чередуется между false и true, начиная с false. Насколько известно компилятору, первый аргумент dynamic для первого && может оценивать этот тип, поэтому он должен быть пессимистичным.

Если тогда он позволяет компилировать код, это может произойти:

  • Когда динамическое связующее оценивает первый &&, он выполняет следующие действия:
    • Оценить первый аргумент
    • Это a T - неявно отбрасывает его на bool.
    • О, это false, поэтому нам не нужно оценивать второй аргумент.
    • Сделать результат && в качестве первого аргумента. (Нет, не false, по какой-то причине.)
  • Когда динамическое связующее оценивает второй &&, оно выполняет следующие действия:
    • Оцените первый аргумент.
    • Это a T - неявно отбрасывает его на bool.
    • О, это true, поэтому оцените второй аргумент.
    • ... О, дерьмо, b не назначено.

В терминах спецификаций, короче говоря, существуют специальные правила "определенного назначения", которые позволяют говорить не только о том, является ли переменная "определенно назначенной" или "не определенно назначена", но также и если она "определенно назначена после false" или "определенно назначается после true statement".

Они существуют, поэтому при работе с && и ||! и ?? и ?:) компилятор может проверить, могут ли переменные назначаться в определенных ветвях сложного булева выражения.

Однако они работают только тогда, когда типы выражений остаются логическими. Когда часть выражения dynamic (или небулевой статический тип), мы уже не можем надежно говорить, что выражение true или false - в следующий раз, когда мы передаем его в bool, чтобы решить, какая ветвь чтобы взять, он, возможно, изменил свое мнение.


Обновление: теперь это разрешено и документально

Определенные правила назначения, реализованные предыдущими компиляторами для динамических выражений, допускают некоторые случаи кода, которые могут привести к чтению переменных, которые определенно не назначены. См. https://github.com/dotnet/roslyn/issues/4509 для одного отчета об этом.

...

Из-за этой возможности компилятор не должен компилировать эту программу, если val не имеет начального значения. Предыдущие версии компилятора (до VS2015) позволили этой программе скомпилироваться, даже если val не имеет начального значения. Рослин теперь диагностирует эту попытку прочитать, возможно, неинициализированную переменную.

Ответ 4

Это не ошибка. См. https://github.com/dotnet/roslyn/issues/4509#issuecomment-130872713 в качестве примера того, как динамическое выражение этой формы может оставить эту переменную переменной неназначенной.