Roslyn Only Compiler Error with Lambda Statements:无法将表达式转换为表达式树
Roslyn Only Compiler Error with Lambda Statements: Expression cannot be converted into an expression tree
问题
我如何重写下面的最小起订量测试,以便它们在 Roslyn 中再次工作?显然我可以删除 Lambda,但我希望避免这种情况。
此外,出于兴趣考虑:Roslyn 编译器是否修复了允许这些无效语句以前工作的错误,或者这是现在破坏这些语句的编译器错误?
详情
我正在尝试将我主要 VB.NET 的解决方案从 Visual Studio 2013 年转移到 Visual Studio 2015 年。解决方案中的所有项目都以 .NET 4.5 为目标。我目前正在使用 Moq 4.0 进行这些测试。我有几个基于 Moq 的 Lambda 单元测试无法在 Visual Studio 2015 年编译,但在 Visual Studio 2013 年编译并 运行 正常。这些测试在 Visual Studio 2010 年和 Visual Studio 2012年也是
大多数测试都非常简单,看起来像这样:
Private _view As Mock(Of Views.ICreateSecurityUserView)
<Test>
Public Sub ValidateSave_CallWithBlankLogin_SetsViewToolError()
_view = New Mock(Of Views.ICreateSecurityUserView)()
_view.SetupGet(Of String)(Function(x) x.Login).Returns("")
_view.SetupGet(Of String)(Function(x) x.LoginName).Returns(loginNameValue)
_subject.ValidateSave()
_view.Verify(Sub(x) x.LoginFieldError = It.Is(Of String)(Function(s) Not String.IsNullOrEmpty(s)), Times.Once)
End Sub
违规行将是这一行:_view.Verify(Sub(x) x.LoginFieldError = It.Is(Of String)(Function(s) Not String.IsNullOrEmpty(s)), Times.Once)
我得到的构建错误(如标题所示)是:
Error BC36534 Expression cannot be converted into an expression tree.
我稍微修改了一下表达式,看看编译器是否会更快乐 multi-line:
_view.Verify(Sub(x)
x.LoginFieldError = It.Is(Of String)(Function(s)
Return Not String.IsNullOrEmpty(s)
End Function)
End Sub, Times.Once)
但无济于事,因为这只会增加这些额外的错误(Visual Studio 2013 编译器也不喜欢 multi-line 版本):
Error BC36675 Statement lambdas cannot be converted to expression trees.
不好"Solution"
如果我将测试行更改为:
,我就可以进行编译
_view.Verify(Sub(x) VerifyFunctionNameError(x), Times.Once)
然后调用新的、毫无意义的函数:
Private Sub VerifyFunctionNameError(x As Views.ICreateSecurityFunctionView)
x.FunctionNameError = It.Is(Of String)(Function(s) Not String.IsNullOrEmpty(s))
End Sub
你的问题很难回答,在不知道声明的情况下我无法得到像样的复现。然而,肯定有一个提示为什么这不应该从 VB.NET 语言规范编译。在您的计算机上的 C:\Program Files (x86)\Microsoft Visual Studio 14.0\VB\Specifications33\Visual 基本语言 Specification.docx
中找到它的副本
导航到第 11.1.1 章并向下滚动一点以到达注释块。我将引用最相关的部分:
The exact translation between lambda methods and expression trees may not be fixed between versions of the compiler and is beyond the scope of this specification. For Microsoft Visual Basic 11.0, all lambda expressions may be converted to expression trees subject to the following restrictions:
1. Only single-line lambda expressions without ByRef parameters may be converted to expression trees. Of the single-line Sub lambdas, only invocation statements may be converted to expression trees.
第一个值得注意的细节是 Microsoft 不想确定确切的规则并且该规范已过时。他们不想钉钉子的原因很明显,限制非常严重,他们本来想留出改进的空间。
它明确说明了为什么编译器版本都不能转换你的多行 lambda 方法,只支持单行。句子的下一部分解释了为什么你不能让它在 VS2015 上工作,你使用的是赋值语句而不是方法调用。
所以这强烈暗示VS2013有问题,它不应该接受你的lambda方法。可以肯定的是,非常激烈的 Roslyn 解析器重写将收紧语法规则并避免复制以前版本中的错误。也有足够的空间担心 VS2013 代码实际上没有正常工作。将 lambda 错误地解析为表达式而不是语句的可能性非零。换句话说,获取比较表达式而不是赋值语句。 VB.NET 确实为这种歧义打开了大门,=
运算符既用于表达式又用于赋值语句。这是一个猜测,我无法在没有复制的情况下进行验证。
最好向实际编写编译器的人反映这一点。您可以在 connect.microsoft.com 提交反馈报告。一定要包括他们可以编译的最小重现,否则他们会很快放弃。请注意,他们很可能会将其关闭为 "By design"。这就是它的样子。
我上面的 "Bad Solution" 我的问题,实际上根本不管用。这在测试运行时失败了,因为它显然无法从子程序中确定任何内容。
我的问题的实际答案是使用 Mock.VerifySet
,而不是 Mock.Verify
。我们实际上在大多数地方都这样做,所以我不确定为什么我们在这里使用其他方法。将在 2013 年 和 2015 年运行的重写测试将如下所示:
_view.VerifySet(Sub(x) x.LoginFieldError = It.Is(Of String)(Function(s) Not String.IsNullOrEmpty(s)), Times.Once)
编译成功,测试也通过了。
我今天在 Moq 源代码中查找,结果不同的原因如下:
验证
public void Verify(Expression<Action<T>> expression, Times times)
{
Mock.Verify(this, expression, times, null);
}
验证集
public void VerifySet(Action<T> setterExpression, Times times)
{
Mock.VerifySet(this, setterExpression, times, null);
}
Expression<Action<T>>
的处理方式显然已更改,而 Action<T>
似乎没有更改。
问题
我如何重写下面的最小起订量测试,以便它们在 Roslyn 中再次工作?显然我可以删除 Lambda,但我希望避免这种情况。
此外,出于兴趣考虑:Roslyn 编译器是否修复了允许这些无效语句以前工作的错误,或者这是现在破坏这些语句的编译器错误?
详情
我正在尝试将我主要 VB.NET 的解决方案从 Visual Studio 2013 年转移到 Visual Studio 2015 年。解决方案中的所有项目都以 .NET 4.5 为目标。我目前正在使用 Moq 4.0 进行这些测试。我有几个基于 Moq 的 Lambda 单元测试无法在 Visual Studio 2015 年编译,但在 Visual Studio 2013 年编译并 运行 正常。这些测试在 Visual Studio 2010 年和 Visual Studio 2012年也是
大多数测试都非常简单,看起来像这样:
Private _view As Mock(Of Views.ICreateSecurityUserView)
<Test>
Public Sub ValidateSave_CallWithBlankLogin_SetsViewToolError()
_view = New Mock(Of Views.ICreateSecurityUserView)()
_view.SetupGet(Of String)(Function(x) x.Login).Returns("")
_view.SetupGet(Of String)(Function(x) x.LoginName).Returns(loginNameValue)
_subject.ValidateSave()
_view.Verify(Sub(x) x.LoginFieldError = It.Is(Of String)(Function(s) Not String.IsNullOrEmpty(s)), Times.Once)
End Sub
违规行将是这一行:_view.Verify(Sub(x) x.LoginFieldError = It.Is(Of String)(Function(s) Not String.IsNullOrEmpty(s)), Times.Once)
我得到的构建错误(如标题所示)是:
Error BC36534 Expression cannot be converted into an expression tree.
我稍微修改了一下表达式,看看编译器是否会更快乐 multi-line:
_view.Verify(Sub(x)
x.LoginFieldError = It.Is(Of String)(Function(s)
Return Not String.IsNullOrEmpty(s)
End Function)
End Sub, Times.Once)
但无济于事,因为这只会增加这些额外的错误(Visual Studio 2013 编译器也不喜欢 multi-line 版本):
Error BC36675 Statement lambdas cannot be converted to expression trees.
不好"Solution"
如果我将测试行更改为:
,我就可以进行编译_view.Verify(Sub(x) VerifyFunctionNameError(x), Times.Once)
然后调用新的、毫无意义的函数:
Private Sub VerifyFunctionNameError(x As Views.ICreateSecurityFunctionView)
x.FunctionNameError = It.Is(Of String)(Function(s) Not String.IsNullOrEmpty(s))
End Sub
你的问题很难回答,在不知道声明的情况下我无法得到像样的复现。然而,肯定有一个提示为什么这不应该从 VB.NET 语言规范编译。在您的计算机上的 C:\Program Files (x86)\Microsoft Visual Studio 14.0\VB\Specifications33\Visual 基本语言 Specification.docx
中找到它的副本导航到第 11.1.1 章并向下滚动一点以到达注释块。我将引用最相关的部分:
The exact translation between lambda methods and expression trees may not be fixed between versions of the compiler and is beyond the scope of this specification. For Microsoft Visual Basic 11.0, all lambda expressions may be converted to expression trees subject to the following restrictions:
1. Only single-line lambda expressions without ByRef parameters may be converted to expression trees. Of the single-line Sub lambdas, only invocation statements may be converted to expression trees.
第一个值得注意的细节是 Microsoft 不想确定确切的规则并且该规范已过时。他们不想钉钉子的原因很明显,限制非常严重,他们本来想留出改进的空间。
它明确说明了为什么编译器版本都不能转换你的多行 lambda 方法,只支持单行。句子的下一部分解释了为什么你不能让它在 VS2015 上工作,你使用的是赋值语句而不是方法调用。
所以这强烈暗示VS2013有问题,它不应该接受你的lambda方法。可以肯定的是,非常激烈的 Roslyn 解析器重写将收紧语法规则并避免复制以前版本中的错误。也有足够的空间担心 VS2013 代码实际上没有正常工作。将 lambda 错误地解析为表达式而不是语句的可能性非零。换句话说,获取比较表达式而不是赋值语句。 VB.NET 确实为这种歧义打开了大门,=
运算符既用于表达式又用于赋值语句。这是一个猜测,我无法在没有复制的情况下进行验证。
最好向实际编写编译器的人反映这一点。您可以在 connect.microsoft.com 提交反馈报告。一定要包括他们可以编译的最小重现,否则他们会很快放弃。请注意,他们很可能会将其关闭为 "By design"。这就是它的样子。
我上面的 "Bad Solution" 我的问题,实际上根本不管用。这在测试运行时失败了,因为它显然无法从子程序中确定任何内容。
我的问题的实际答案是使用 Mock.VerifySet
,而不是 Mock.Verify
。我们实际上在大多数地方都这样做,所以我不确定为什么我们在这里使用其他方法。将在 2013 年 和 2015 年运行的重写测试将如下所示:
_view.VerifySet(Sub(x) x.LoginFieldError = It.Is(Of String)(Function(s) Not String.IsNullOrEmpty(s)), Times.Once)
编译成功,测试也通过了。
我今天在 Moq 源代码中查找,结果不同的原因如下:
验证
public void Verify(Expression<Action<T>> expression, Times times)
{
Mock.Verify(this, expression, times, null);
}
验证集
public void VerifySet(Action<T> setterExpression, Times times)
{
Mock.VerifySet(this, setterExpression, times, null);
}
Expression<Action<T>>
的处理方式显然已更改,而 Action<T>
似乎没有更改。