Combine Lambda Expressions

I am looking for a way to combine two lambda expressions without using Expression.Invoke in any expression. I essentially want to build a new expression that combines two separate ones. Consider the following code:

 class Model { public SubModel SubModel { get; set;} } class SubModel { public Foo Foo { get; set; } } class Foo { public Bar Bar { get; set; } } class Bar { public string Value { get; set; } } 

And let's say I had two expressions:

 Expression<Func<Model, Foo>> expression1 = m => m.SubModel.Foo; Expression<Func<Foo, string>> expression2 = f => f.Bar.Value; 

And I want to combine them together to functionally get the following expression:

 Expression<Func<Model, string>> joinedExpression = m => m.SubModel.Foo.Bar.Value; 

The only way I could do this is to use ExpressionVisitor as follows:

 public class ExpressionExtender<TModel, TIntermediate> : ExpressionVisitor { private readonly Expression<Func<TModel, TIntermediate>> _baseExpression; public ExpressionExtender(Expression<Func<TModel, TIntermediate>> baseExpression) { _baseExpression = baseExpression; } protected override Expression VisitMember(MemberExpression node) { _memberNodes.Push(node.Member.Name); return base.VisitMember(node); } private Stack<string> _memberNodes; public Expression<Func<TModel, T>> Extend<T>(Expression<Func<TIntermediate, T>> extend) { _memberNodes = new Stack<string>(); base.Visit(extend); var propertyExpression = _memberNodes.Aggregate(_baseExpression.Body, Expression.Property); return Expression.Lambda<Func<TModel, T>>(propertyExpression, _baseExpression.Parameters); } } 

And then it can be used as follows:

 var expExt = new ExpressionExtender<Model, Foo>(expression1); var joinedExpression = expExt.Extend(expression2); 

It works, but for me it is a bit awkward. I am still trying to wrap my expressions and wonder if there is a more idiomatic way of expressing this, and I have a hidden suspicion that I will miss something obvious.


The reason I want to do this is to use it with ASP.NET mvc 3 Html helpers. I have some deeply nested ViewModels and some HtmlHelper extensions that help deal with them, so the expression should just be a MemberExpressions set for MVC built-in helpers to handle them correctly and create the correct deeply nested name attribute values. My first instinct was to use Expression.Invoke() and call the first expression and associate it with the second, but MVC helpers didn't really like that. He lost his hierarchical context.

+7
source share
3 answers

Using a visitor to replace all instances of the parameter f with m.SubModel.Foo and create a new expression with m as the parameter:

 internal static class Program { static void Main() { Expression<Func<Model, Foo>> expression1 = m => m.SubModel.Foo; Expression<Func<Foo, string>> expression2 = f => f.Bar.Value; var swap = new SwapVisitor(expression2.Parameters[0], expression1.Body); var lambda = Expression.Lambda<Func<Model, string>>( swap.Visit(expression2.Body), expression1.Parameters); // test it worked var func = lambda.Compile(); Model test = new Model {SubModel = new SubModel {Foo = new Foo { Bar = new Bar { Value = "abc"}}}}; Console.WriteLine(func(test)); // "abc" } } class SwapVisitor : ExpressionVisitor { private readonly Expression from, to; public SwapVisitor(Expression from, Expression to) { this.from = from; this.to = to; } public override Expression Visit(Expression node) { return node == from ? to : base.Visit(node); } } 
+21
source

Your solution seems narrowly tailored to your specific problem, which seems inflexible.

It seems to me that you could solve your problem simply enough with a simple lambda replacement: replace instances of the parameter (or "free variable", as they call it in the lambda calculus) with the body. (See Marc's answer for some code to do this.)

Since the parameter in the expression trees has a reference identifier and not an identity value, there is no need to rename their alpha.

That is, you have:

 Expression<Func<A, B>> ab = a => f(a); // could be *any* expression using a Expression<Func<B, C>> bc = b => g(b); // could be *any* expression using b 

and you want to create a composition

 Expression<Func<A, C>> ac = a => g(f(a)); // replace all b with f(a). 

So, take body g(b) , ask for a visitor to search and replace, looking for ParameterExpression for b , and replace it with body f(a) to give you a new body g(f(a)) . Then create a new lambda with parameter a that this body has.

+6
source

Update: below the response generates an "Invoke" that does not support EF.

I know this is an old thread, but I have the same need, and I figured out a cleaner way to do it. Assuming you can change your "expression2" to a user with a common lambda, you can enter one like this:

 class Program { private static Expression<Func<T, string>> GetValueFromFoo<T>(Func<T, Foo> getFoo) { return t => getFoo(t).Bar.Value; } static void Main() { Expression<Func<Model, string>> getValueFromBar = GetValueFromFoo<Model>(m => m.SubModel.Foo); // test it worked var func = getValueFromBar.Compile(); Model test = new Model { SubModel = new SubModel { Foo = new Foo { Bar = new Bar { Value = "abc" } } } }; Console.WriteLine(func(test)); // "abc" } } 
0
source

All Articles