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?
NET Compiler Platform, also known by its codename Roslyn, is a set of open-source compilers and code analysis APIs for C# and Visual Basic (VB.NET) languages from Microsoft.
Roslyn is a complete rewrite of the C# and VB.net compilers with each written in their respective language for example the C# compiler is written in C# rather than C++.
Roslyn is a collection of open-source compilers, code analysis and refactoring tools which work with C# and Visual Basic source codes. This set of compilers and tools can be used to create full-fledged compilers, including, first and foremost, source code analysis tools.
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.
This does appear to be a bug, or at the least a regression, in the Roslyn compiler. The following bug has been filed to track it:
https://github.com/dotnet/roslyn/issues/4509
In the meantime, Jon's excellent answer has a couple of work arounds.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With