Reusing the result of an expression in a parent expression
Let's say I have a function that merges two children Expression
(all Expression
will return int
) according to the "ab + a" rule:
public Expression CustomCombine(Expression a, Expression b)
{
Expression mult = Expression.Multiply(a, b);
return Expression.Add(mult, a);
}
If I compile and run the resulting one Expression
, is it enough for the compiler to know that the argument a
should not be explicitly double checked internally? Trees Expression
for a
and b
can be quite long and expensive.
If in fact the resultant Expression
will evaluate twice a
, then what is the best way to cache the value for reuse?
For example, I was wondering if maybe this would work (but it is not clear to me if it can ConstantExpression
get the result Expression
determined at runtime):
public Expression CustomCombine(Expression a, Expression b)
{
Expression aVal = Expression.Constant(a);
Expression mult = Expression.Multiply(aVal, b);
return Expression.Add(mult, aVal);
}
Note. I know this could be refactored as "a (b + 1)", but my original question holds true as I have other combinations that can be reused a
or b
many times over in ways that cannot be similar. For example, "a 2 + ab + b 2 ".
source to share
Yes, in the case of the original expression, it will be evaluated twice, but you can cache it. Expression.Constant
won't work in this case as it requires a constant value and needs to be evaluated at runtime of the expression, but you can use Expression.Variable
to declare a new variable and then Expression.Assign
store the value a
for that variable and then declare Exression.Block
with those expressions as it Variable
requires a custom block ... You can do the same for the variable b
if you like.
The following code will generate an expression like this:
(obj) => { int cached = obj.A; return cached*obj.B + cached; }
Here's some sample code:
using System;
using System.Linq.Expressions;
public class Program
{
public static void Main(string[] args)
{
ParameterExpression param = Expression.Parameter(typeof(Test), "obj");
Expression a = Expression.Property(param, "A");
Expression b = Expression.Property(param, "B");
Expression result = CustomCombine(a, b);
var lambda = Expression.Lambda<Func<Test, int>>(result, new ParameterExpression[] { param });
Func<Test, int> func = lambda.Compile();
var obj = new Test();
var val = func(obj);
Console.WriteLine("Result is " + val);
}
private static Expression CustomCombine(Expression a, Expression b)
{
var variable = Expression.Variable(a.Type, "cached");
var aVal = Expression.Assign(variable, a);
var mult = Expression.Multiply(variable, b);
var result = Expression.Add(mult, variable);
// here we are making Block with variable declaration and assigment
var block = Expression.Block(new ParameterExpression[]{variable}, aVal, result);
return block;
}
}
public class Test
{
public int A
{
get
{
Console.WriteLine("Property A is accessed");
return 42;
}
}
public int B
{
get
{
return 1;
}
}
}
And a working .NetFiddle sample is https://dotnetfiddle.net/bfYVbv
source to share