2012-02-03 88 views
7

我正在寻找一种方式两个lambda表达式结合起来,没有在任的表达使用Expression.Invoke。我想基本上建立一个链接两个单独的表达式的新表达式。请看下面的代码:结合Lambda表达式

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<Model, Foo>> expression1 = m => m.SubModel.Foo; 
Expression<Func<Foo, string>> expression2 = f => f.Bar.Value; 

而且我想加入他们一起在功能上得到下面的表达式:

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

唯一这样我可以认为这样做是为了使用这样的ExpressionVisitor:

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

然后将其像这样使用:

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

它的工作原理,但感觉有点笨重给我。我仍然试图包裹头部表情,并想知道是否有更通俗的方式来表达这一点,而且我有一种偷偷摸摸的怀疑,我错过了一些明显的东西。


原因我想这样做是为了与ASP.net MVC 3 HTML辅助使用它。我有一些深层嵌套的ViewModels和一些HtmlHelper扩展来帮助处理这些扩展,所以表达式只需要一个MemberExpressions的集合,用于内置的MVC帮助程序正确处理它们并构建正确深层嵌套的名称属性值。我的第一本能是使用Expression.Invoke(),并调用第一个表达式并将其链接到第二个表达式,但MVC帮助者并不那么喜欢。它失去了它的分层上下文。

回答

21

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

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); 
    } 
} 
+0

+1这使我很明白现在看到它。有一件事我没有在原始问题中提及:有没有办法做到这一点,而不会改变开始表达式。例如,我有一个核心表达式,我需要以几种不同的方式进行扩展,每次调用都会生成新的表达式。 – 2012-02-03 18:00:51

+0

@ 32bitkid是的!表达是不可改变的;我没有突变他们中的任何一个! – 2012-02-03 18:14:23

+0

再次感谢您的帮助。 – 2012-02-03 18:21:39

6

您的解决方案似乎是狭义根据您的具体问题,这似乎不灵活。

对我来说,你可以通过简单的拉姆达替代直截了当足够解决问题:更换参数(或“自由变”,因为他们把它称为演算)与身体的实例。 (见马克的回答一些代码来做到这一点。)

由于表达式树的参数有参照的身份,而不是价值认同,甚至没有必要的α重命名。

也就是说,你必须:

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 

,并要产生组成

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

于是把尸体g(b),做搜索和替换游客寻找​​为b,并与主体f(a)替换它给你新的身体g(f(a))。然后使用具有该主体的参数a来创建一个新的lambda。

+0

@Kobi:我不明白这个问题。是*还有可能吗?而'a'和'b'不是lambdas;他们是形式参数。 – 2012-02-03 17:40:15

+0

这个解决方案适用于两个'Expression's,还是只适用于两个'Func <>'s? (好吧,没关系 - 我想我现在明白你的答案) – Kobi 2012-02-03 17:41:26

+0

@Kobi:我不明白你的意思是“合适”。假设你有常数1的表达式和常量2的表达式。你希望对这两个表达式进行什么操作,这与lambda表达式上的函数组合相似? – 2012-02-03 17:48:04

0

更新:下面的答案会生成一个EF不支持的“Invoke”。

我知道这是一个古老的线程,但我有同样的需要,我想出了一个更干净的方式来做到这一点。假设你可以改变你的“表达式2”给用户一个通用的拉姆达,你可以注入一个像这样的:

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