2008-12-06 37 views
12

Tôi có một cây biểu thức mà tôi đã tạo bằng cách phân tích một Xml bằng cách sử dụng lớp biểu thức trong C#. See this question.Làm cách nào để biên dịch một cây biểu thức thành một phương thức có thể gọi được, C#?

Tôi chỉ có Thêm, Trừ, Chia, Nhân, Tham số, Và và trong Cây biểu thức của tôi. Có cách nào để chuyển đổi ExpressionTree này thành một phương thức có thể gọi được không? ... hoặc tôi có phải phát ra IL theo cách thủ công không?

Trân trọng!

+2

Các ví dụ khác được thêm vào ... –

Trả lời

10

Đây là ví dụ về cả hai cách tiếp cận. Nếu tôi đã bỏ lỡ một cái gì đó, hoặc bạn muốn biết thêm thông tin, chỉ cần cho tôi biết.

static void Main() 
{ 
    // try to do "x + (3 * x)" 

    var single = BuildSingle<decimal>(); 
    var composite = BuildComposite<decimal>(); 

    Console.WriteLine("{0} vs {1}", single(13.2M), composite(13.2M)); 
} 
// utility method to get the 3 as the correct type, since there is not always a "int x T" 
static Expression ConvertConstant<TSource, TDestination>(TSource value) 
{ 
    return Expression.Convert(Expression.Constant(value, typeof(TSource)), typeof(TDestination)); 
} 
// option 1: a single expression tree; this is the most efficient 
static Func<T,T> BuildSingle<T>() 
{   
    var param = Expression.Parameter(typeof(T), "x"); 
    Expression body = Expression.Add(param, Expression.Multiply(
     ConvertConstant<int, T>(3), param)); 
    var lambda = Expression.Lambda<Func<T, T>>(body, param); 
    return lambda.Compile(); 
} 
// option 2: nested expression trees: 
static Func<T, T> BuildComposite<T>() 
{ 

    // step 1: do the multiply: 
    var paramInner = Expression.Parameter(typeof(T), "inner"); 
    Expression bodyInner = Expression.Multiply(
     ConvertConstant<int, T>(3), paramInner); 
    var lambdaInner = Expression.Lambda(bodyInner, paramInner); 

    // step 2: do the add, invoking the existing tree 
    var paramOuter = Expression.Parameter(typeof(T), "outer"); 
    Expression bodyOuter = Expression.Add(paramOuter, Expression.Invoke(lambdaInner, paramOuter)); 
    var lambdaOuter = Expression.Lambda<Func<T, T>>(bodyOuter, paramOuter); 

    return lambdaOuter.Compile(); 
} 

Cá nhân, tôi sẽ hướng tới phương pháp đầu tiên; nó đơn giản và hiệu quả hơn. Điều này có thể liên quan đến việc truyền tham số ban đầu trong suốt một chồng mã lồng nhau, nhưng cũng vậy. Tôi đã có một số mã ở đâu đó mà có cách tiếp cận "Gọi" (composite), và viết lại cây như cách tiếp cận đầu tiên (duy nhất) - nhưng nó khá phức tạp và lâu dài. Nhưng rất hữu ích cho Entity Framework (không hỗ trợ Expression.Invoke).

+0

Điều đó đã làm các trick ... Cảm ơn rất nhiều. –

12

Bạn cần phải tạo một lambda - ví dụ:

var lambda = Expression.Lambda<Func<float,int>>(body, param); 
Func<float,int> method = lambda.Compile(); 
int v = method(1.0); // test 

nơi "cơ thể" là cây biểu hiện của bạn (tham gia một float, trả lại một int) liên quan đến việc ParameterExpression param.

Bạn cũng có thể tìm thấy thisthis hữu ích.

+0

Vấn đề là ((x + 2) + y)/z Khi có nhiều hơn một tham số của các biểu thức phụ khác nhau trong cây, tôi phải làm gì? –

+0

Có thể có nhiều tham số (đó là một tham số của ParameterExpression); cho biểu thức con, bạn cần gọi biểu thức bên trong (Expression.Invoke?) –

+0

Bạn cũng có thể xây dựng toàn bộ cây thành một biểu thức duy nhất; điều này có hiệu quả hơn một chút, nhưng phức tạp hơn để làm. –

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