二元表达式转换为Lambda函数

5
这可能对一些人来说很熟悉。我有一个包装类Ex,用一堆隐式转换和运算符包装表达树。这里是简化版。
public class Ex 
{
    Expression expr;

    public Ex(Expression expr)
    {
        this.expr = expr;
    }
    public static implicit operator Expression(Ex rhs) { return rhs.expr; }
    public static implicit operator Ex(double value) 
    { return new Ex(Expression.Constant(value, typeof(double))); }
    public static implicit operator Ex(string x) 
    { return new Ex(Expression.Parameter(typeof(double), x)); }
    public static Ex operator +(Ex left, Ex right)
    {
        return new Ex(Expression.Add(left, right));
    }
    public static Ex operator -(Ex rhs)
    {
        return new Ex(Expression.Negate(rhs));
    }
    public static Ex operator -(Ex left, Ex right)
    {
        return new Ex(Expression.Subtract(left, right));
    }
    public static Ex operator *(Ex left, Ex right)
    {
        return new Ex(Expression.Multiply(left, right));
    }
    public static Ex operator /(Ex left, Ex right)
    {
        return new Ex(Expression.Divide(left, right));
    }
}

所以这是我想要做的事情:
{ ...
    Ex x = "x";
    Ex y = 10.0;
    Ex z = x + y;

    LambdaExpression lambda = BuildLambda(z);
    Func<double,double> f = (Func<double,double>)lambda.Compile();

    // f(5) = 15

}

但是如何正确遍历树并构建我的lambda(或委托)呢?

    LambdaExpression BuildLambda(Expression e)
    {
        ConstantExpression cex = e as ConstantExpression;
        if(cex != null)
        {
            return Expression.Lambda<Func<double>>( cex );
        }
        ParameterExpression pex = e as ParameterExpression;
        if (pex != null)
        {
            Func<Expression, Expression> f = (x) => x;
            Expression body = f(pex);
            return Expression.Lambda<Func<double, double>>( body , pex);
        }
        BinaryExpression bex = e as BinaryExpression;
        if (bex != null)
        {
            LambdaExpression left = GetLambda(bex.Left);
            LambdaExpression rght = GetLambda(bex.Right);
   // Now what?
        }
        return null;
    }

我已经尝试了几种方法将二进制表达式 bex 转换为 lambda,但到目前为止都没有成功。我希望能得到一些建议和指针。请注意,操作数可能是其他表达式对象,并且只有在树的叶子节点上它们才会是 ParameterExpression 或 ConstantExpression。
谢谢。
2个回答

6

您可以在调用转换运算符时创建表达式树:

public class Ex
{
    private readonly Expression expr;

    public Ex(Expression expr)
    {
        this.expr= expr;
    }

    public Expression Expression
    {
        get { return this.expr; }
    }

    public static Ex operator +(Ex left, Ex right)
    {
        return new Ex(Expression.Add(left.expr, right.expr));
    }                                       ↑           ↑

    // etc.
}

每一步,您都需要从Ex实例中“解包”Expression,应用Expression.*方法,并将结果封装在一个新的Ex实例中。

最后,您只需从最终的Ex实例中提取Expression即可:

Ex x = new Ex(Expression.Parameter(typeof(double), "x"));
Ex y = new Ex(Expression.Constant(10.0, typeof(double)));
Ex z = x + y;

Expression<Func<double, double>> result =
    Expression.Lambda<Func<double, double>>(z.Expression, x.Expression);

请注意,C#编译器提供了一个功能,可以为您创建表达式树:
Expression<Func<double, double>> result = x => x + 10.0;

这段代码创建了与上述代码完全相同的表达式树。


创建树并不是问题。我的 Ex x = "x" 运行得很好。通过 Ex x = "x"; Ex y = 10.0; Ex z = x + y; Expression<Func<double, double>> lambda = Expression.Lambda<Func<double, double>>((Expression) z, (ParameterExpression) (Expression) x); Func<double, double> f3 = (Func<double, double>)lambda.Compile(); double result = f3(5); // result = 15 解决了创建代理的问题。所以非常感谢。诀窍就是去做它,让编译器解决细节问题。 - John Alexiou
你有没有关于从代码构建表达式树并编译成委托的参考/示例/教程?下一步是将其扩展到方法/函数调用,除了上面所做的简单算术运算。 - John Alexiou
@jalexiou:我认为你的Ex类可能是多余的——你似乎复制了很多已经存在的函数。如果你想动态定义表达式树,可以使用Expression.*方法。如果你想静态定义表达式树,C#编译器可以为你生成它们。如果你只需要一个静态的lambda表达式,你根本不需要表达式树。你想做什么? - dtb

0
如果您的表达式都来自于一个共同的类,可以在Gamma等人的书中查找“访问者”模式。这甚至是他们使用的示例。

“System.Linq.Expressions.ExpressionVisitor” 是一个内部类,我无法继承它。 - John Alexiou
2
@jalexiou:System.Linq.Expressions.ExpressionVisitor在.NET 4.0中被公开。http://msdn.microsoft.com/en-us/library/system.linq.expressions.expressionvisitor.aspx - dtb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接