How to optimize LINQ expressions?
-
14-11-2019 - |
Question
On a project built with .NET 3.5, I am using LINQ expressions to dynamically generate code at runtime. The LINQ expressions are compiled using the Compile method and stored for later use as predicates with LINQ to objects.
The expressions are sometimes quite complicated and difficult to debug.
Below is an example of an expression viewed through the debugger visualizer in Visual Studio.
{request => (Invoke(workEnvelopeHead => (workEnvelopeHead.Method = value(Wombl.Scenarios.CannedResponses+<>c_DisplayClass58).pipeline), request.WorkEnvelope.Head) And Invoke(body => Invoke(value(Wombl.Scenarios.CannedResponses+<>c_DisplayClass78).isMatch, body.SingleOrDefault()),Convert(request.WorkEnvelope.Body.Any)))}
I would like to be able to optimize expressions like the above so that the value(Wombl.Scenarios.CannedResponses+<>c__DisplayClass58).pipeline
expression is replaced with a constant that is the variable's value.
In this particular case, value(Wombl.Scenarios.CannedResponses+<>c__DisplayClass58).pipeline
is a reference in the lambda to a variable in the parent scope. Something like:
var pipeline = "[My variable's value here]";
// My lambda expression here, which references pipeline
// Func<RequestType, bool> predicate = request => ........ workEnvelopeHead.Method == pipeline ..........
The original expression, optimized ought to look like:
{request => (Invoke(workEnvelopeHead => (workEnvelopeHead.Method = "[My variable's value here]", request.WorkEnvelope.Head) And Invoke(body => > Invoke(value(Wombl.Scenarios.CannedResponses+<>c__DisplayClass78).isMatch, body.SingleOrDefault()),Convert(request.WorkEnvelope.Body.Any)))}
How can I make such optimizations at runtime to the LINQ expression, before compiling?
Solution
So I went ahead and wrote an expression visitor that replaces the variable references with the actual value. It wasn't so hard to do after all.
Usage:
var simplifiedExpression = ExpressionOptimizer.Simplify(complexExpression);
The class:
It inherits from ExpressionVisitor which came from the code samples on this page because in .NET 3.0 it is internal. In .NET 4.0 the class is public but might require some changes to this class.
public sealed class ExpressionOptimizer : ExpressionVisitor
{
private ExpressionOptimizer()
{
}
#region Methods
public static Expression<TDelegate> Simplify<TDelegate>(Expression<TDelegate> expression)
{
return expression == null
? null
: (Expression<TDelegate>) new ExpressionOptimizer().Visit(expression);
}
private static bool IsPrimitive(Type type)
{
return type.IsPrimitive
|| type.IsEnum
|| type == typeof (string)
|| type == typeof (DateTime)
|| type == typeof (TimeSpan)
|| type == typeof (DateTimeOffset)
|| type == typeof (Decimal)
|| typeof(Delegate).IsAssignableFrom(type);
}
protected override Expression VisitMemberAccess(MemberExpression memberExpression)
{
var constantExpression = memberExpression.Expression as ConstantExpression;
if (constantExpression == null || !IsPrimitive(memberExpression.Type))
return base.VisitMemberAccess(memberExpression);
// Replace the MemberExpression with a ConstantExpression
var constantValue = constantExpression.Value;
var propertyInfo = memberExpression.Member as PropertyInfo;
var value = propertyInfo == null
? ((FieldInfo) memberExpression.Member).GetValue(constantValue)
: propertyInfo.GetValue(constantValue, null);
return Expression.Constant(value);
}
#endregion
}