在Linq.Expressions中调用lambda遇Closure参数问题的解决方案咨询
解决方案
示例代码报错原因
你代码里的CreateLambda()返回的是LambdaExpression类型,而CallAndDouble方法要求第一个入参是Func<double, double>委托类型,直接将表达式树作为参数传入Expression.Call无法匹配方法签名,所以运行失败。
最简修复方案
不需要获取lambda对应的MethodInfo,也完全不需要接触内部的Closure类,你只需要将lambda编译为委托实例,再包装为ConstantExpression传入即可:
static Func<double, double> DoubleIt() { var input = Expression.Parameter(typeof(double), "x"); // 先将动态生成的lambda表达式树编译为委托 var squareFunc = (Func<double, double>)CreateLambda().Compile(); // 把委托实例包装为常量表达式传入 var call = Expression.Call( typeof(Program).GetMethod(nameof(CallAndDouble)), Expression.Constant(squareFunc, typeof(Func<double, double>)), input ); return Expression.Lambda<Func<double, double>>(call, input).Compile(); }
修改后运行代码会输出32,符合预期。
公式求值器场景优化方案
如果你需要避免提前编译lambda的开销,或者要对表达式树做整体简化、内联优化,可以直接把lambda的执行逻辑展开到调用处,不需要走委托传递:
static Func<double, double> DoubleIt() { var input = Expression.Parameter(typeof(double), "x"); var lambda = (LambdaExpression)CreateLambda(); // 替换lambda参数为当前入参,等效于展开f(x)+f(x)的逻辑 var squareBody = ReplaceParameter(lambda.Body, lambda.Parameters[0], input); var add = Expression.Add(squareBody, squareBody); return Expression.Lambda<Func<double, double>>(add, input).Compile(); } // 辅助方法:替换表达式树中的参数 static Expression ReplaceParameter(Expression body, ParameterExpression oldParam, ParameterExpression newParam) { return new ParameterReplacer(oldParam, newParam).Visit(body); } class ParameterReplacer : ExpressionVisitor { private readonly ParameterExpression _oldParam; private readonly ParameterExpression _newParam; public ParameterReplacer(ParameterExpression oldParam, ParameterExpression newParam) { _oldParam = oldParam; _newParam = newParam; } protected override Expression VisitParameter(ParameterExpression node) { return node == _oldParam ? _newParam : base.VisitParameter(node); } }
带Closure参数的MethodInfo相关说明
C#编译器生成lambda的Method属性带Closure参数是框架内部实现细节,不要依赖这个实现做调用。如果真的要直接调用该MethodInfo,对于没有捕获外部变量的lambda,第一个参数传null即可,但这种方式兼容性极差,非常不推荐,优先使用上述委托常量的方案。
内容的提问来源于stack exchange,提问作者Ian Marteens
相关产品推荐
相关产品推荐

