为什么不同版本的.net(或编译器)会为相同表达式生成不同的表达式树?

5
在我的一个库中,我有一段代码,它从表达式中返回一个MethodInfo:
public MethodInfo GetMethod(Expression expression)
{
    var lambdaExpression = (LambdaExpression)expression;
    var unaryExpression = (UnaryExpression)lambdaExpression.Body;
    var methodCallExpression = (MethodCallExpression)unaryExpression.Operand;
    var methodInfoExpression = (ConstantExpression)methodCallExpression.Arguments.Last();
    return (MethodInfo)methodInfoExpression.Value;
}

我有一系列助手函数,以实现如下所示的调用:

public MethodInfo Func<T, R, A1>(Expression<Func<T, Func<A1, R>>> expression)
{
    return GetMethod(expression);
}

这将使以下语法可用:

这将启用以下语法:

var methodInfo = Func<TestClass, bool, string>(x => x.AnInstanceMethodThatTakesAStringAndReturnsABool);

这个方法最开始运行得很好,直到我最近将库升级到了 .Net 4.6.1 和最新的 c# 编译器。

在之前的 .net 版本中,表达式的形式是:

{x => Convert(CreateDelegate(System.Func`2[System.String, System.Boolean], x, Boolean AnInstanceMethodThatTakesAStringAndReturnsABool(System.String)))}

因此,我的代码会将methodCallExpression的最后一个参数作为methodInfoExpression查找。
现在,在.Net 4.6.1(最新的c#编译器)中,编译器似乎生成了一个不同形式的表达式:
{x => Convert(Boolean AnInstanceMethodThatTakesAStringAndReturnsABool(System.String).CreateDelegate(System.Func`2[System.String, System.Boolean], x))}

我的当前代码出现错误,因为最后一个参数不是常量表达式。看起来很容易解决,只需要将其改为

 var methodInfoExpression = (ConstantExpression)methodCallExpression.Object;

显然,GetMethod函数相当脆弱,容易受编译器生成表达式的影响。 我很好奇这种更改的原因以及如何重构GetMethod函数,使其更能适应编译器生成的表达式树。


1
当提供示例时,您应该真正发布可以编译的示例代码。如果您想要帮助更有效地编写方法,您应该描述它需要做什么,而不仅仅展示不起作用的版本。 - Servy
1个回答

4
从.NET 4.5开始,有两种从MethodInfo创建委托的方法:一个是在MethodInfo上创建实例方法,另一个是使用静态方法Delegate.CreateDelegate。看起来Microsoft决定切换到使用#1,为了某个原因(可能更有效率)。如果想让GetMethod更加弹性,以适应编译器生成的表达式树,你可以使用表达式访问器,并通过这种方式查找方法信息。请参考此处链接:https://msdn.microsoft.com/en-us/library/bb882521%28v=vs.90%29.aspx?f=255&MSPPError=-2147217396

非常有帮助。谢谢! - Joe Enzminger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接