2015-08-12 4 views
94

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

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

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

Error CS0165 Use of unassigned local variable 'b'

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

+11

@BinaryWorrier: Почему? Он использует только 'b' после назначения его через параметр' out'. –

+1

[Документация VS 2015 говорит] (https://msdn.microsoft.com/en-us/library/kx37x362.aspx) «Хотя переменные, переданные как внешние аргументы, не должны быть инициализированы перед передачей, вызываемый метод требуется присвоить значение перед возвратом метода. " так что это выглядит как ошибка да, она гарантированно будет инициализирована этим tryParse. – Rup

+0

Да, я тоже удивлен. Как я знаю, 'out' требует назначения внутри метода. Так что эта ошибка немного странная. – ramil89

ответ

111

What does cause this issue?

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

Однако, оказывается, что через оператор и преобразование сложного, это вполне возможно, чтобы иметь выражение A && B && C который оценивает A и C но не B - если вы хитрый достаточно. См. Roslyn bug report для изобретательного примера Нила Гафтера.

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

Is it possible to fix it through compiler settings?

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

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

Если вы действительно нужно держать его динамичным, просто дать b значение, чтобы начать с:

decimal a, b = 0m; 

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

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

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

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

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

+0

Да! Применение «IEnumerable » или добавление скобок работало для меня. Теперь компилятор строит без ошибок. – ramil89

+1

с использованием 'decimal a, b = 0m;' может удалить ошибку, но тогда 'a <= b' всегда будет использовать' 0m', так как значение out еще не рассчитано. –

+12

@PawBaltzersen: Почему вы так думаете? Он всегда * будет * назначен * перед сравнением - это просто, что компилятор не может это доказать, почему-то (ошибка, в основном). –

21

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

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

В то же время, Джона excellent answer имеет несколько рабочих обходные.

+0

Также [эта ошибка] (https://github.com/dotnet/roslyn/issues/4507), которая отмечает, что теперь это связано с LINQ ... – Rawling

16

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


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

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

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

С точки зрения спецификации, короче говоря, существуют специальные «определенное назначение» правила, которые позволяют нам говорить не только о том, как переменная «определенно присвоенной» или «определенно не назначен», но и если оно «определенно присвоено после false заявление» или «определенно присвоено после true заявление».

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

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


Update: теперь это был resolved и documented:

The definite assignment rules implemented by previous compilers for dynamic expressions allowed some cases of code that could result in variables being read that are not definitely assigned. See https://github.com/dotnet/roslyn/issues/4509 for one report of this.

...

Because of this possibility the compiler must not allow this program to be compiled if val has no initial value. Previous versions of the compiler (prior to VS2015) allowed this program to compile even if val has no initial value. Roslyn now diagnoses this attempt to read a possibly uninitialized variable.

+1

Используя VS2013 на моей другой машине, мне действительно удалось прочитать неназначенные память используя это. Это не очень интересно :( – Rawling

+0

Вы можете прочитать неинициализированные переменные с простым делегатом. Создайте делегат, который получает 'out' к методу, который имеет' ref'. Он с радостью сделает это, и он будет присвоить переменные без изменения значения – IllidanS4

+0

Из любопытства я тестировал этот фрагмент с C# v4. Однако из любопытства - как компилятор решил использовать оператор 'false' /' true' в отличие от неявного оператора трансляции? Локально он будет называть ' неявный оператор bool' по первому аргументу, затем вызовите второй операнд, вызовите 'operator false' в первом операнде, а затем« неявный оператор bool »в первом операнде * снова *. Это не имеет смысла для меня, первый операнд должен, по существу, сводиться к логическому однажды, no? – Rob

15

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

+1

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

+0

Большое спасибо за ваше замечательное доказательство. – ramil89

Смежные вопросы