After I have migrated my project from VS2013 to VS2015 the project no longer builds. A compilation error occurs in the following LINQ statement:
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();
}
The compiler returns an error:
Error CS0165 Use of unassigned local variable 'b'
What causes this issue? Is it possible to fix it through a compiler setting?
What does cause this issue?
Looks like a compiler bug to me. At least, it did. Although the decimal.TryParse(v, out a)
and decimal.TryParse(v, out b)
expressions are evaluated dynamically, I expected the compiler to still understand that by the time it reaches a <= b
, both a
and b
are definitely assigned. Even with the weirdnesses you can come up with in dynamic typing, I'd expect to only ever evaluate a <= b
after evaluating both of the TryParse
calls.
However, it turns out that through operator and conversion tricky, it's entirely feasible to have an expression A && B && C
which evaluates A
and C
but not B
- if you're cunning enough. See the Roslyn bug report for Neal Gafter's ingenious example.
Making that work with dynamic
is even harder - the semantics involved when the operands are dynamic are harder to describe, because in order to perform overload resolution, you need to evaluate operands to find out what types are involved, which can be counter-intuitive. However, again Neal has come up with an example which shows that the compiler error is required... this isn't a bug, it's a bug fix. Huge amounts of kudos to Neal for proving it.
Is it possible to fix it through compiler settings?
No, but there are alternatives which avoid the error.
Firstly, you could stop it from being dynamic - if you know that you'll only ever use strings, then you could use IEnumerable<string>
or give the range variable v
a type of string
(i.e. from string v in array
). That would be my preferred option.
If you really need to keep it dynamic, just give b
a value to start with:
decimal a, b = 0m;
This won't do any harm - we know that actually your dynamic evaluation won't do anything crazy, so you'll still end up assigning a value to b
before you use it, making the initial value irrelevant.
Additionally, it seems that adding parentheses works too:
where decimal.TryParse(v, out a) && (decimal.TryParse("15", out b) && a <= b)
That changes the point at which various pieces of overload resolution are triggered, and happens to make the compiler happy.
There is one issue still remaining - the spec's rules on definite assignment with the &&
operator need to be clarified to state that they only apply when the &&
operator is being used in its "regular" implementation with two bool
operands. I'll try to make sure this is fixed for the next ECMA standard.