Roslyn无法编译代码

ram*_*l89 95 .net c# linq 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();
}
Run Code Online (Sandbox Code Playgroud)

编译器返回错误:

错误CS0165使用未分配的局部变量'b'

是什么导致这个问题?是否可以通过编译器设置来修复它?

Jon*_*eet 112

是什么导致了这个问题?

看起来像编译器错误给我.至少,确实如此.虽然decimal.TryParse(v, out a)decimal.TryParse(v, out b)表现进行动态评估,我希望编译器还了解到,通过它到达的时间a <= b,无论是ab肯定分配.即使你可以在动态类型中提出奇怪的想法,我也希望a <= b在评估两个TryParse调用后才能进行评估.

然而,事实证明,通过操作符和转换变得棘手,有一个表达式A && B && C可以评估A,C但不是B- 如果你足够狡猾的话.有关Neal Gafter巧妙的例子,请参阅Roslyn错误报告.

使用dynamic它更加困难 - 当操作数是动态时所涉及的语义更难描述,因为为了执行重载解析,您需要评估操作数以找出涉及的类型,这可能是违反直觉的.然而,Neal再次提出了一个示例,该示例显示编译器错误是必需的...这不是错误,它是一个错误修复.为了证明这一点,Neal获得了大量的赞誉.

是否可以通过编译器设置来修复它?

不,但有其他方法可以避免错误.

首先,你可以阻止它变得动态 - 如果你知道你只会使用字符串,那么你可以使用IEnumerable<string> 给范围变量v一种类型string(即from string v in array).那将是我的首选.

如果你真的需要保持动态,只需给出b一个值:

decimal a, b = 0m;
Run Code Online (Sandbox Code Playgroud)

这不会造成任何伤害 - 我们知道实际上您的动态评估不会做任何疯狂的事情,因此您b在使用它之前仍然会最终分配值,从而使初始值无关紧要.

此外,似乎添加括号也有效:

where decimal.TryParse(v, out a) && (decimal.TryParse("15", out b) && a <= b)
Run Code Online (Sandbox Code Playgroud)

这改变了触发各种重载决策的点,并使编译器感到高兴.

还有一个问题仍然存在 - 规范与&&运营商明确分配的规则需要明确说明它们仅在&&运营商在其"常规"实施中使用两个bool操作数时才适用.我将尝试确保这是针对下一个ECMA标准修复的.

  • @PawBaltzersen:是什么让你这么想?它总是*将*在比较之前被分配 - 这只是编译器无法证明它,出于某种原因(基本上是一个bug). (12认同)

Jar*_*Par 21

这似乎是Roslyn编译器中的错误,或者至少是回归.已提交以下错误以跟踪它:

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

与此同时,Jon的优秀答案还有几个方面.


Raw*_*ing 16

由于我在错误报告中受过如此刻苦的教育,我将尝试自己解释一下.


想象一下T是一些用户定义的类型,带有隐式强制转换bool,在它之间交替,false并从中true开始false.就编译器所知,第dynamic一个参数&&可能会评估为该类型,因此它必须是悲观的.

如果,那么,它让代码编译,这可能发生:

  • 当动态绑定器评估第一个时&&,它执行以下操作:
    • 评估第一个参数
    • 这是一个T- 隐含地投入其中bool.
    • 哦,是的false,所以我们不需要评估第二个参数.
    • &&evaluate 的结果作为第一个参数.(不,不是false,出于某种原因.)
  • 当动态绑定器评估第二个时&&,它执行以下操作:
    • 评估第一个参数.
    • 这是一个T- 隐含地投入其中bool.
    • 哦,是的true,所以评估第二个论点.
    • ...哦废话,b没有分配.

简而言之,有一些特殊的"明确赋值"规则,它们不仅可以说明变量是"明确赋值"还是"未明确赋值",而且还可以false说明" 它是在声明后明确赋值"还是"绝对赋值"在true声明后分配".

这些存在使得与处理时&&||(和!???:)编译器可以检查是否变量可以在复杂的布尔表达式的特定分支被分配.

但是,这些只在表达式的类型保持布尔值时才起作用.当表达式的一部分是dynamic(或非布尔静态类型)时,我们再也无法可靠地说表达式是- true或者false- 下次我们将其转换bool为决定采用哪个分支时,它可能已经改变了主意.


更新:现在已经解决记录在案:

先前编译器为动态表达式实现的明确赋值规则允许某些代码可能导致读取的变量未明确赋值.有关此问题的报告,请参阅https://github.com/dotnet/roslyn/issues/4509.

...

由于这种可能性,如果val没有初始值,编译器不得允许编译该程序.先前版本的编译器(在VS2015之前)允许该程序编译,即使val没有初始值.罗斯林现在诊断这种尝试读取可能未初始化的变量.


Nea*_*ter 15

这不是一个错误.有关此表单的动态表达式如何使这样的out变量未分配的示例,请参阅https://github.com/dotnet/roslyn/issues/4509#issuecomment-130872713.