2012-02-03 57 views
7

Tôi đang tìm cách kết hợp hai biểu thức lambda, không sử dụng Expression.Invoke trên một trong hai biểu thức. Tôi muốn về cơ bản xây dựng một biểu thức mới có hai chuỗi riêng biệt. Xét đoạn mã sau:Kết hợp Biểu thức 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; } 
} 

Và cho phép nói rằng tôi đã có hai biểu thức:

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

Và tôi muốn tham gia cùng họ với nhau để chức năng được biểu thức sau đây:

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

duy nhất cách tôi có thể nghĩ để làm điều này là sử dụng ExpressionVisitor như thế này:

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

Và sau đó sử dụng nó như thế này:

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

Nó hoạt động, nhưng nó cảm thấy một chút thời gian cho tôi. Tôi vẫn đang cố gắng che đi những biểu cảm đầu của mình và tự hỏi liệu có cách nào để thể hiện điều này một cách thành ngữ hơn không, và tôi có một nghi ngờ lén lút rằng tôi thiếu một điều gì đó hiển nhiên.


Các lý do Tôi muốn làm điều này là sử dụng nó với ASP.net MVC 3 người giúp đỡ Html. Tôi có một số ViewModels lồng nhau và một số phần mở rộng HtmlHelper giúp giải quyết chúng, vì vậy biểu thức cần phải là một bộ sưu tập của MemberExpressions cho những người trợ giúp MVC được xây dựng để xử lý chúng một cách chính xác và xây dựng các giá trị thuộc tính tên lồng nhau một cách chính xác. Bản năng đầu tiên của tôi là sử dụng Expression.Invoke() và gọi biểu thức đầu tiên và chuỗi nó vào thứ hai, nhưng những người trợ giúp MVC không thích điều đó lắm. Nó mất bối cảnh phân cấp của nó.

Trả lời

21

Sử dụng một người truy cập để trao đổi tất cả các trường hợp thông số f để m.SubModel.Foo, và tạo ra một biểu thức mới với m như tham số:

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 Điều này có ý nghĩa rất nhiều khi tôi nhìn thấy nó. Một điều tôi đã không đề cập đến trong câu hỏi ban đầu: là có một cách để làm điều này mà không có đột biến hoặc bắt đầu biểu hiện. Ví dụ, tôi có một biểu thức cốt lõi mà tôi cần phải mở rộng theo nhiều cách khác nhau, tạo ra các biểu thức mới với mỗi lời gọi. –

+0

@ 32bitkid có! biểu thức là bất biến; Tôi đã không đột biến một trong hai người họ! –

+0

Cảm ơn bạn rất nhiều vì đã giúp đỡ. –

6

giải pháp của bạn dường như được thu hẹp lại phù hợp với vấn đề cụ thể của bạn, mà dường như không linh hoạt. Dường như với tôi rằng bạn có thể giải quyết vấn đề của bạn một cách đơn giản, đủ thông qua thay thế lambda đơn giản: thay thế các tham số của biến (hoặc "biến tự do" khi chúng gọi nó trong phép tính lambda) với phần thân. (Xem câu trả lời của Marc cho một số mã để làm như vậy.)

Vì tham số trong cây biểu thức có định danh tham chiếu hơn là nhận dạng giá trị, thậm chí không cần phải đổi tên chúng.

Đó là, bạn có:

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 

và bạn muốn tạo ra các thành phần

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

Vì vậy, có cơ thể g(b), thực hiện tìm kiếm và thay thế người truy cập tìm kiếm ParameterExpression cho b và thay thế bằng nội dung f(a) để cung cấp cho bạn cơ thể mới g(f(a)).Sau đó tạo một lambda mới với tham số a có phần thân đó.

+0

@Kobi: Tôi không hiểu câu hỏi. Là * những gì * vẫn có thể? Và 'a' và' b' không phải là lambdas; chúng là các tham số chính thức. –

+0

Giải pháp này có phù hợp với hai 'Biểu thức', hay chỉ cho hai 'Func <>' s? (ok, đừng bận tâm - Tôi nghĩ tôi đã hiểu câu trả lời của bạn ngay bây giờ) – Kobi

+0

@Kobi: Tôi không hiểu ý của bạn là "phù hợp". Giả sử bạn có biểu thức cho hằng số 1 và biểu thức cho hằng số 2. Bạn muốn thực hiện thao tác nào trên hai biểu thức tương tự với thành phần hàm trên lambdas? –

0

Cập nhật: câu trả lời dưới đây tạo ra một "Gọi" mà EF không hỗ trợ.

Tôi biết đây là một chủ đề cũ, nhưng tôi có cùng nhu cầu và tôi đã tìm ra một cách rõ ràng hơn để làm điều đó. Giả sử rằng bạn có thể thay đổi "expression2" của bạn thành người dùng lambda chung, bạn có thể tiêm một cái như sau:

Các vấn đề liên quan