2012-02-03 86 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" 
    } 
}