结合Lambda表达式

我正在寻找一种方法来组合两个lambda表达式,而不在任何一个Expression.Invoke上使用Expression.Invoke 。 我想基本上构建一个链接两个独立的表达式的新表达式。 请考虑以下代码:

 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; } } 

我可以说我有两个表达方式:

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

我想将它们连接在一起以在function上获得以下表达式:

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

我能想到的唯一方法是使用这样的ExpressionVisitor:

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

然后用它像这样:

 var expExt = new ExpressionExtender(expression1); var joinedExpression = expExt.Extend(expression2); 

它有效,但对我来说感觉有点笨拙。 我仍然试图包裹我的头脑表达,并想知道是否有一种更惯用的方式来表达这一点,我有一种偷偷摸摸的怀疑,我错过了一些明显的东西。


我想这样做的原因是将它与ASP.net mvc 3 Html助手一起使用。 我有一些深度嵌套的ViewModel和一些HtmlHelper扩展来帮助处理这些,所以表达式只需MemberExpressions内置MVC帮助程序的MemberExpressions集合来正确处理它们并构建正确深度嵌套的名称属性值。 我的第一直觉是使用Expression.Invoke()并调用第一个表达式并将其链接到第二个表达式,但MVC帮助程序并不那么喜欢。 它失去了等级背景。

使用访问者将参数f所有实例交换到m.SubModel.Foo ,并使用m作为参数创建一个新表达式:

 internal static class Program { static void Main() { Expression> expression1 = m => m.SubModel.Foo; Expression> expression2 = f => f.Bar.Value; var swap = new SwapVisitor(expression2.Parameters[0], expression1.Body); var lambda = Expression.Lambda>( 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); } } 

您的解决方案似乎针对您的具体问题进行了狭窄的调整,这似乎不灵活。

在我看来,你可以通过简单的lambda替换直接解决你的问题:用body取代参数的实例(或者在lambda演算中称为“自由变量”)。 (参见Marc对某些代码的回答。)

由于表达式树中的参数具有引用标识而不是值标识,因此甚至不需要对它们进行alpha重命名。

也就是说,你有:

 Expression> ab = a => f(a); // could be *any* expression using a Expression> bc = b => g(b); // could be *any* expression using b 

你希望生产这种成分

 Expression> ac = a => g(f(a)); // replace all b with f(a). 

所以取身体g(b) ,做一个搜索和替换访问者寻找bParameterExpression ,并用身体f(a)替换它给你新的身体g(f(a)) 。 然后使用具有该主体的参数a创建一个新的lambda。

更新:以下答案生成EF不支持的“调用”。

我知道这是一个老线程,但我有同样的需求,我想出了一个更清洁的方法来做到这一点。 假设您可以将“expression2”更改为用户通用lambda,您可以注入如下:

 class Program { private static Expression> GetValueFromFoo(Func getFoo) { return t => getFoo(t).Bar.Value; } static void Main() { Expression> getValueFromBar = GetValueFromFoo(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" } }