Expression.Block()可以在lambda闭包中返回一个值吗?

时间:2017-05-08 22:12:06

标签: c# .net expression-trees

我想将两个单独的表达式树编译成单个编译的lambda。我有一个double[]输入数组。第一个表达式树(为了简单起见,我们将其称为ExpressionA)创建一个长度相同的新double[]数组,其中包含输入数组值转换的结果。第二个表达式树(ExpressionB)对转换后的数组进行一些计算,并返回一个double输出,我想返回。

我认为以下内容可行,但我遇到了问题:

ParameterExpression inputArray = Expression.Parameter(typeof(double[]));
ParameterExpression xformArray = Expression.Parameter(typeof(double[]));

Func<double[], double> compiled = Expression.Lambda<Func<double[], double>>(
    Expression.Block(new ParameterExpression[] { inputArray, xformArray },
        Expression.Assign(xformArray, ExpressionA(inputArray)),
        ExpressionB(xformArray)),
    inputArray).Compile();

虽然程序构建,但是当我调用编译函数时,我得到一个NullReference运行时异常(堆栈跟踪没有用,因为它没有进入lambda_method())。

然而,这个更简单的版本运行得很好(只是传入xformed数组):

Func<double[], double> compiled = Expression.Lambda<Func<double[], double>>(
    ExpressionB(xformArray)), xformArray).Compile();

但是这个更简单的版本也因NullReference异常而失败:

Func<double[], double> compiled = Expression.Lambda<Func<double[], double>>(
    Expression.Block(new ParameterExpression[] { xformArray },
        ExpressionB(xformArray)),
    xformArray).Compile();

最后,我也尝试了这个概念验证版本,它也有效(让我相信一个Block在概念上应该在lambda中是正常的):

Func<double[], double> compiled = Expression.Lambda<Func<double[], double>>(
    Expression.Block(new ParameterExpression[] { inputArray, xformArray },
        Expression.Constant(0.0)), // stub test
    inputArray).Compile();

所以我的问题是如何在单个编译的lambda中以顺序方式使用两个表达式树?

1 个答案:

答案 0 :(得分:2)

没有好的Minimal, Complete, and Verifiable code example,特别是没有关于ExpressionA()ExpressionB()是什么以及您实际使用它们的细节,我们无法确定最佳答案可能是什么是。但是,我看到的最明显的问题是你重新声明inputArray,它在块中创建了一个新的局部变量。如果没有分配任何东西,它的值当然是null

修复方法是从块的变量中删除它,只留下xformArray

Func<double[], double> compiled = Expression.Lambda<Func<double[], double>>(
    Expression.Block(new ParameterExpression[] { xformArray },
        Expression.Assign(xformArray, ExpressionA(inputArray)),
        ExpressionB(xformArray)),
    inputArray).Compile();

同样,如果没有一个好的MCVE,就不可能确切知道你的选择是什么。但恕我直言,总是更喜欢在代码中表达表达式而不是手动构建它们。 E.g:

Func<double[], double> MakeExpression(
    Func<double[], double[]> transformA,
    Func<double[], double> transformB)
{
    return a => transformB(transformA(a));
}

如果变换本身由于某种原因需要表达为表达式,你仍然可以在构建lambda的其余部分之前单独编译它们:

Func<double[], double> MakeExpression(
    Expression<Func<double[], double[]>> transformA,
    Expression<Func<double[], double>> transformB)
{
    Func<double[], double[]> transformACompiled = transformA.Compile();
    Func<double[], double> transformBCompiled = transformB.Compile();

    return a => transformBCompiled(transformACompiled(a));
}

但是,如果必须明确地使用Expression类完成整个事情,那么上面第一个代码示例中的更正应该可以解决您的问题。

最后,我会指出您的原始代码可以大大简化:

    Func<double[], double> compiled = Expression.Lambda<Func<double[], double>>(
        ExpressionB(ExpressionA(inputArray)),
        inputArray).Compile();

当然,在您的真实代码中,您可能会有更复杂的表达方式。但至少对于帖子中的示例,您甚至不需要块或中间局部变量。