I'd like to compile two separate expression trees into a single compiled lambda. I have a double[]
input array. The first expression tree (for the sake of simplicity, let's refer to as ExpressionA
) creates a new double[]
array of the same length, which contains the results of a transformation of the input array values. The second expression tree (ExpressionB
) does some calculations on the transformed array and returns a single double
output, which I'd like to return.
I thought the following would work, but I am having problems:
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();
Although the program builds, I get a NullReference runtime exception when I called the compiled function (stack trace isn't helpful since it doesn't get inside lambda_method()
).
However, this simpler version runs just fine (just passing in the xformed array):
Func<double[], double> compiled = Expression.Lambda<Func<double[], double>>(
ExpressionB(xformArray)), xformArray).Compile();
But this simpler version also fails with the NullReference exception:
Func<double[], double> compiled = Expression.Lambda<Func<double[], double>>(
Expression.Block(new ParameterExpression[] { xformArray },
ExpressionB(xformArray)),
xformArray).Compile();
Lastly, I also tried this proof-of-concept version and it also works (leading me to believe that a Block
should conceptually be OK inside a lambda):
Func<double[], double> compiled = Expression.Lambda<Func<double[], double>>(
Expression.Block(new ParameterExpression[] { inputArray, xformArray },
Expression.Constant(0.0)), // stub test
inputArray).Compile();
So my question is how do I use both expression trees in a sequential manner inside a single compiled lambda?
Without a good Minimal, Complete, and Verifiable code example, and especially without specifics about what ExpressionA()
and ExpressionB()
are and how you're actually using them, it's impossible to know for sure what the best answer might be. However, the most apparent issue I see is that you are re-declaring inputArray
, which creates a new local variable in the block. Having not been assigned to anything, its value is of course null
.
The fix would be to just remove that from the block's variables, leaving only xformArray
:
Func<double[], double> compiled = Expression.Lambda<Func<double[], double>>(
Expression.Block(new ParameterExpression[] { xformArray },
Expression.Assign(xformArray, ExpressionA(inputArray)),
ExpressionB(xformArray)),
inputArray).Compile();
Again, without a good MCVE, it's impossible to know for sure what your options might be. But IMHO it is always preferable to express expressions in code instead of building them manually. E.g.:
Func<double[], double> MakeExpression(
Func<double[], double[]> transformA,
Func<double[], double> transformB)
{
return a => transformB(transformA(a));
}
If the transforms themselves are for some reason required to be expressed as expressions, you can still compile them individually before building the rest of the 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));
}
But, if you must do the whole thing using the Expression
class explicitly, the correction in the first code example above should address your concern.
Finally, I'll point out that your original code can be significantly simplified:
Func<double[], double> compiled = Expression.Lambda<Func<double[], double>>(
ExpressionB(ExpressionA(inputArray)),
inputArray).Compile();
Of course, in your real code you may have a more complicated expression. But at least for the example in your post, you don't even need the block or the intermediate local variable.