2014-11-18 37 views
5

Tôi biết tôi có thể tạo cây biểu thức sử dụng:Sử dụng một biểu thức trong một cây biểu hiện trình biên dịch tạo

  1. phương pháp Factory.

  2. Chuyển đổi trình biên dịch biểu thức lambda thành Expression.

Đối với cây biểu thức phức tạp, tôi thích 2 vì nó ngắn gọn hơn.

Có thể tham chiếu đến đã được xây dựng Expressions bằng cách này không?

using System; 
using System.Linq.Expressions; 

public class Test 
{ 
    public static Expression<Func<int, int>> Add(Expression expr) 
    { 
#if false 
     // works 
     ParameterExpression i = Expression.Parameter(typeof(int)); 
     return Expression.Lambda<Func<int, int>>(Expression.Add(i, expr), i); 
#else 
     // compiler error, can I pass expr here somehow? 
     return i => i + expr; 
#endif 
    } 

    public static void Main() 
    { 
     Func<int, int> f = Add(Expression.Constant(42)).Compile(); 
     Console.WriteLine(f(1)); 
    } 
} 
+0

tôi nghĩ rằng tất cả các bạn cần là để xác định loại biểu hiện trong tiện ích từ Expression để biểu vì tôi nghĩ đó là những gì đang xảy ra trong ví dụ trên nhưng nó được phỏng đoán. –

Trả lời

2

Bạn không thể kết hợp tùy ý Expression trường hợp với cây biểu thức biên dịch theo thời gian. Những gì bạn có thể làm là xây dựng một cây biểu thức mới với các nút cụ thể được thay thế, vì vậy bạn có thể có i => i + marker và sau đó xây dựng một cây mới với nút được thay thế bằng biểu thức thời gian chạy của bạn. Điều này đòi hỏi viết một thích hợp ExpressionVisitor:

public static class ExpressionExtensions { 
    public static T AsPlaceholder<T>(this Expression expression) { 
    throw new InvalidOperationException(
     "Expression contains placeholders." 
    ); 
    } 

    public static Expression FillPlaceholders(this Expression expression) { 
    return new PlaceholderExpressionVisitor().Visit(expression); 
    } 
} 

class PlaceholderExpressionVisitor : ExpressionVisitor { 
    protected override Expression VisitMethodCall(MethodCallExpression node) { 
    if (
     node.Method.DeclaringType == typeof(ExpressionExtensions) && 
     node.Method.Name == "AsPlaceholder" // in C# 6, we would use nameof() 
    ) { 
     return Expression.Lambda<Func<Expression>>(node.Arguments[0]).Compile()(); 
    } else { 
     return base.VisitMethodCall(node); 
    } 
    } 
} 

Add lúc này là:

public static Expression<Func<int, int>> Add(Expression expr) { 
    Expression<Func<int, int>> add = i => i + expr.AsPlaceholder<int>(); 
    return (Expression<Func<int, int>>) add.FillPlaceholders(); 
} 

Khái niệm hơi khó hiểu

Expression.Lambda<Func<Expression>>(node.Arguments[0]).Compile()() 

có thể được giải thích bằng cách quan sát rằng trình biên dịch sẽ nắm bắt những biểu hiện chúng tôi chèn lại trong một đóng cửa, bất kể nó đến từ đâu, do đó, đối số của cuộc gọi phương thức luôn là một tham chiếu đến việc đóng cửa này mà chúng tôi ne ed để đánh giá để có được biểu hiện thực tế.

Tỷ lệ này với một số lượng tùy ý các biểu thức thay thế với số lần nhập tối thiểu rõ ràng.

+0

Tôi đã sửa đổi câu trả lời này để đi từ một cách khác nhau bằng cách sử dụng 'ReplaceVisitor' của Servy sang một cách tiếp cận mới, nơi chúng tôi sử dụng các biểu thức như là phần giữ chỗ. –

7

Không có gì ngoài hộp, nhưng bạn có thể tự mình xây dựng một công cụ để cung cấp chức năng này.

Bạn có thể viết một phương thức chấp nhận một biểu thức có hai tham số, một tham số "thực" và một tham số của một số giá trị mà bạn muốn thay thế bằng giá trị của biểu thức khác. Sau đó bạn có thể có một biểu thức giải quyết để giá trị đó và thay thế tất cả các trường hợp của các tham số với biểu thức thứ hai:

public static Expression<Func<TSource, TResult>> BuildExpression 
    <TSource, TOther, TResult>(
    Expression<Func<TSource, TOther, TResult>> function, 
    Expression<Func<TOther>> innerExpression) 
{ 
    var body = function.Body.Replace(function.Parameters[1], innerExpression.Body); 
    return Expression.Lambda<Func<TSource, TResult>>(body, function.Parameters[0]); 
} 

Bạn có thể sử dụng phương pháp sau đây để thay thế tất cả các trường hợp của một biểu thức với nhau:

public static Expression Replace(this Expression expression, 
    Expression searchEx, Expression replaceEx) 
{ 
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression); 
} 
internal class ReplaceVisitor : ExpressionVisitor 
{ 
    private readonly Expression from, to; 
    public ReplaceVisitor(Expression from, Expression to) 
    { 
     this.from = from; 
     this.to = to; 
    } 
    public override Expression Visit(Expression node) 
    { 
     return node == from ? to : base.Visit(node); 
    } 
} 

sau đó bạn có thể áp dụng nó vào trường hợp của bạn như sau:

public static Expression<Func<int, int>> Add(Expression<Func<int>> expr) 
{ 
    return BuildExpression((int i, int n) => i + n, expr); 
} 
+0

Rất đẹp.Vì lý do nào đó, tôi đã nhớ rằng bạn cần phải ghi đè lên tất cả các thành viên của 'ExpressionVisitor', nhưng tất nhiên chỉ là' .Visit() 'sẽ làm. Tôi chắc chắn một cái gì đó chung chung hơn có thể được đưa ra cho 'BuildExpression', mặc dù - nó không phải là rất rõ ràng rằng nó thay thế tham số thứ hai với một biểu thức, và mở rộng này để nhiều giá trị thay thế tương tự như vậy sẽ là một nỗi đau. –

+0

@JeroenMostert Genericising nó đến một mức độ lớn hơn là tất nhiên có thể, nhưng phần nào làm việc nhiều hơn, vì vậy, vì mục đích đơn giản, tôi giới hạn phạm vi chỉ những gì cần thiết để giải quyết vấn đề này, để nó vẫn có thể hiểu được. – Servy

+0

Tôi đã cập nhật câu trả lời của mình bằng cách thay thế tiềm năng bằng cách sử dụng 'ReplaceVisitor'. Tôi nghĩ tôi sẽ tiếp tục tìm Chén Thánh trong một thời gian để tập thể dục. :-) –

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