Say I have a function that combines two child Expression
(all Expression
will return an int
) according to the rule "ab + a":
public Expression CustomCombine(Expression a, Expression b)
{
Expression mult = Expression.Multiply(a, b);
return Expression.Add(mult, a);
}
If I compile and run the resultant Expression
, is the compiler smart enough to know that the argument a
doesn't need to be explicitly evaluated twice internally? The Expression
trees for a
and b
may be quite lengthy and expensive.
If, in fact, the resultant Expression
would evaluate a
twice, 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 unclear to me if an ConstantExpression
can hold an Expression
result 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 am aware this could be refactored as "a(b+1)" but my original question holds as I have other combinations which may reuse a
or b
many times in ways that can't be similarly factored. For example, "a2 + ab + b2".
Yes, in case of original expression it will be evaluated twice, but you can cache it. Expression.Constant
won't work in that case as it requires constant value, and it needs to be evaluated during expression execution, but you can use Expression.Variable
to declare new variable and then Expression.Assign
to save a
value to that variable and then declare Exression.Block
with these expressions, as Variable
requires own block.
You can do the same for b
variable if you want.
Following code will generate such expression:
(obj) => { int cached = obj.A; return cached*obj.B + cached; }
Here is 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 working .NetFiddle sample - https://dotnetfiddle.net/bfYVbv