【问题标题】:Roslyn failed to compile codeRoslyn 编译代码失败
【发布时间】:2015-11-04 20:06:44
【问题描述】:

在我将项目从 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”

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

【问题讨论】:

  • @BinaryWorrier:为什么?它仅在通过out 参数分配后才使用b
  • The VS 2015 documentation says "虽然作为输出参数传递的变量在被传递之前不必初始化,但被调用的方法需要在方法返回之前分配一个值。"所以这看起来确实像一个错误,是的,它保证由该 tryParse 初始化。
  • 不管错误如何,这段代码都说明了 out 参数的所有缺点。 TryParse 会返回一个可为空的值(或等效值)吗?
  • @KonradRudolph where (a = decimal.TryParse(v)).HasValue &amp;&amp; (b = decimal.TryParse(v)).HasValue &amp;&amp; a &lt;= b 看起来好多更好
  • 请注意,您可以将其简化为decimal a, b; var q = decimal.TryParse((dynamic)"10", out a) &amp;&amp; decimal.TryParse("15", out b) &amp;&amp; a &lt;= b;。我已经 opened a Roslyn bug 提出了这个问题。

标签: c# .net linq roslyn


【解决方案1】:

是什么导致了这个问题?

对我来说看起来像是一个编译器错误。至少,它做到了。尽管decimal.TryParse(v, out a)decimal.TryParse(v, out b) 表达式是动态评估的,但我预计编译器仍然可以理解,当它到达a &lt;= b 时,ab 都已确定分配。即使您可以在动态类型中提出一些奇怪的问题,我也希望在评估两个 TryParse 调用之后才评估 a &lt;= b

然而,事实证明,通过操作符和转换技巧,有一个表达式 A &amp;&amp; B &amp;&amp; C 计算 AC 而不是 B 是完全可行的 - 如果你足够狡猾的话。请参阅 Roslyn bug report 了解 Neal Gafter 的巧妙示例。

使用dynamic 更难——当操作数是动态时所涉及的语义更难描述,因为为了执行重载决议,您需要评估操作数以找出所涉及的类型,这可以违反直觉。然而,Neal 再次提出了一个示例,表明编译器错误是必需的……这不是错误,而是错误修复。非常感谢 Neal 的证明。

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

不,但有替代方法可以避免错误。

首先,你可以阻止它是动态的——如果你知道你只会使用字符串,那么你可以使用IEnumerable&lt;string&gt; 给范围变量v一个类型string(即from string v in array)。那将是我的首选。

如果您真的需要保持动态,只需给b 一个值即可:

decimal a, b = 0m;

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

此外,添加括号似乎也可以:

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

这改变了触发各种重载决议的点,并且恰好使编译器高兴。

一个问题仍然存在 - 需要澄清规范中关于使用 &amp;&amp; 运算符进行明确分配的规则,以说明它们仅适用于在其中使用 &amp;&amp; 运算符时带有两个 bool 操作数的“常规”实现。我会尽量确保这对于下一个 ECMA 标准是固定的。

【讨论】:

  • 是的!应用IEnumerable&lt;string&gt; 或添加括号对我有用。现在编译器构建没有错误。
  • 使用decimal a, b = 0m; 可能会消除错误,但随后a &lt;= b 将始终使用0m,因为尚未计算出值。
  • @PawBaltzersen:是什么让你这么认为?它总是在比较之前被分配 - 只是编译器无法证明它,出于某种原因(基本上是一个错误)。
  • 有一个没有副作用的解析方法,即。 decimal? TryParseDecimal(string txt) 也可能是一个解决方案
  • 不知道是不是懒初始化;它认为“如果第一个是真的,那么我不需要评估第二个,这意味着可能不会分配 b”;我知道这是无效的推理,但它解释了为什么括号修复它......
【解决方案2】:

这似乎是 Roslyn 编译器中的一个错误,或者至少是一个回归。已提交以下错误以对其进行跟踪:

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

与此同时,Jon 的excellent answer 有几个变通办法。

【讨论】:

  • 另外this bug 指出它现在与 LINQ 无关...
【解决方案3】:

由于我在错误报告中受到如此严格的教育,因此我将尝试自己解释这一点。


假设T 是某个用户定义的类型,隐式转换为bool,在falsetrue 之间交替,以false 开头。据编译器所知,第一个 &amp;&amp; 的第一个参数 dynamic 可能评估为该类型,因此它必须是悲观的。

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

  • 当动态绑定器评估第一个 &amp;&amp; 时,它会执行以下操作:
    • 计算第一个参数
    • 这是一个 T - 隐式转换为 bool
    • 哦,是false,所以我们不需要评估第二个参数。
    • &amp;&amp; 的结果作为第一个参数进行评估。 (不,不是false,出于某种原因。)
  • 当动态绑定器评估第二个&amp;&amp; 时,它会执行以下操作:
    • 评估第一个参数。
    • 这是一个 T - 隐式将其转换为 bool
    • 哦,是true,所以计算第二个参数。
    • ...哦,废话,b 未分配。

简而言之,有一些特殊的“确定赋值”规则,让我们不仅可以判断一个变量是“确定赋值”还是“未确定赋值”,还可以判断它是否“在@987654342 之后确定赋值” @语句”或“肯定在true语句之后赋值”。

这些存在以便在处理&amp;&amp;||(以及!???:)时,编译器可以检查是否可以在复杂布尔表达式的特定分支中分配变量。

但是,这些仅在表达式的类型保持布尔值时起作用。当表达式的一部分是dynamic(或非布尔静态类型)时,我们不能再可靠地说表达式是truefalse - 下次我们将它转​​换为bool 来决定哪个分支采取,它可能已经改变了主意。


更新:现在是resolveddocumented

以前的编译器为动态表达式实现的明确分配规则允许某些代码情况可能导致读取的变量未明确分配。请参阅https://github.com/dotnet/roslyn/issues/4509 获取一份报告。

...

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

【讨论】:

  • 在我的另一台机器上使用 VS2013,我实际上已经设法使用它读取未分配的内存。这不是很令人兴奋:(
  • 您可以使用简单的委托读取未初始化的变量。创建一个将out 获取到具有ref 的方法的委托。它会很高兴地做到这一点,它会分配变量,而不改变值。
  • 出于好奇,我用 C# v4 测试了 sn-p。不过,出于好奇 - 编译器如何决定使用运算符 false/true 而不是隐式转换运算符?在本地,它将在第一个参数上调用implicit operator bool,然后调用第二个操作数,在第一个操作数上调用operator false,然后在第一个操作数上调用implicit operator bool再次。这对我来说没有意义,第一个操作数本质上应该归结为一个布尔值,不是吗?
  • @Rob 这是dynamic, chained-&amp;&amp; 的情况吗?我已经看到它基本上是(1)评估第一个参数(2)使用隐式转换来查看我是否可以短路(3)我不能,所以评估第二个参数(4)现在我知道这两种类型,我可以看到最好的&amp;&amp;是用户自定义的&amp;(5)在第一个参数上调用运算符false看能不能短路(6)我能(因为falseimplicit bool不一样) ,所以结果是第一个参数......然后是下一个&amp;&amp;,(7)使用隐式转换来查看我是否可以(再次)短路。
  • @IllidanS4 听起来很有趣,但我还没有找到方法。你能给我一个sn-p吗?
【解决方案4】:

这不是错误。请参阅 https://github.com/dotnet/roslyn/issues/4509#issuecomment-130872713 以了解有关此形式的动态表达式如何使此类 out 变量未赋值的示例。

【讨论】:

  • 由于我的回答被接受并得到高度评价,我对其进行了编辑以表明解决方案。感谢您为此所做的所有工作-包括向我解释我的错误:)
猜你喜欢
  • 2011-01-22
  • 1970-01-01
  • 1970-01-01
  • 2010-12-31
  • 1970-01-01
  • 2022-11-30
  • 1970-01-01
  • 2015-09-13
相关资源
最近更新 更多