在构建更复杂的表达式时如何重用表达式?

Avr*_*oel 9 c# lambda expression-trees

我正在努力学习表达,主要是为了我自己的教育.我正在尝试研究如何构建一个表达比某些东西更复杂的表达式a+b等等.

我将逐步采取这一步骤,这样你就可以看到我是如何构建它的.请随意评论我的方法的任何方面,尽管实际问题出现在第三个代码块上.

我理解如何创建一个将输入除以2的函数:

// Set up a parameter for use below
ParameterExpression x = Expression.Parameter(typeof(double), "x");

Expression two = Expression.Constant((double)2);
Expression halve = Expression.MakeBinary(ExpressionType.Divide, x, two);
// Test it
double halfOfTwenty = Expression.Lambda<Func<double, double>>(halve, x).Compile()(20);
Run Code Online (Sandbox Code Playgroud)

我还研究了如何制作一个计算sin(x)的表达式:

Expression sine = Expression.Call(typeof(Math).GetMethod("Sin"), x);
// Test it
double sinePiOverTwo = Expression.Lambda<Func<double, double>>(sine, x).Compile()(Math.PI / 2);
Run Code Online (Sandbox Code Playgroud)

我现在想做的是创建一个计算sin(x/2)的表达式.我可以这样做......

Expression sineOfHalf = Expression.Call(typeof(Math).GetMethod("Sin"), halve);
Run Code Online (Sandbox Code Playgroud)

...但理想情况下,我想重用我现有的正弦表达式,而不是创建一个新表达式.

我确信这很简单,但对于这个领域的新手,我发现它很难.有谁能告诉我怎么做?我查看了Expression类,但显然忽略了我需要的方法.

Jon*_*nna 5

ParameterExpression x = Expression.Parameter(typeof(double), "x");

Expression two = Expression.Constant((double)2);
Expression halve = Expression.MakeBinary(ExpressionType.Divide, x, two);
Run Code Online (Sandbox Code Playgroud)

好的,此时你有一些代表的东西,x / 2你的下一步创建了lambda表达式x => x / 2.(顺便说一下,你也可以使用Expression.Divide()而不是MakeBinary更简洁.

Expression sine = Expression.Call(typeof(Math).GetMethod("Sin"), x);
Run Code Online (Sandbox Code Playgroud)

此时,您有一个表示的表达式,Math.Sin(x)您的下一步创建了lambda表达式x => Math.Sin(x).

因此,您需要做的是在每次创建lambda表达式之前组合您所处的两个点:

Expression sine = Expression.Call(typeof(Math).GetMethod("Sin"), halve);
Run Code Online (Sandbox Code Playgroud)

现在你可以做到最后一步:

Expression.Lambda<Func<double, double>>(sine, x) // x => Math.Sin(x / 2.0)
Run Code Online (Sandbox Code Playgroud)

整个代码:

ParameterExpression x = Expression.Parameter(typeof(double), "x");
Expression two = Expression.Constant((double)2);
Expression halve = Expression.MakeBinary(ExpressionType.Divide, x, two);
Expression sine = Expression.Call(typeof(Math).GetMethod("Sin"), halve);
Expression<Func<double, double>> sineHalveLambda = Expression.Lambda<Func<double, double>>(sine, x);
Run Code Online (Sandbox Code Playgroud)

并测试:

Func<double, double> f = sineHalveLambda.Compile();
Console.WriteLine(f(Math.PI));  // 1
Console.WriteLine(f(0));        // 0
Console.WriteLine(f(-Math.PI)); // -1
Run Code Online (Sandbox Code Playgroud)

顺便说一句,using static System.Linq.Expressions.Expression;在直接使用Expression类时,在文件中使用它通常很有用,因为你经常使用它的静态成员,然后它有时可以帮助可视化生成的树,如果你把它作为一个单行使用用缩进反映树:

ParameterExpression x = Parameter(typeof(double), "x");
Expression<Func<double, double>> sineHalveLambda = Lambda<Func<double, double>>(
    Call(
        typeof(Math).GetMethod("Sin"),
        Divide(
            x,
            Constant(2.0)
        )
    )
    , x);
Run Code Online (Sandbox Code Playgroud)

因为缩进反映了生成的表达式树的分支.虽然在反映树形结构的可读性优势和一线的一般可读性缺点之间存在平衡.

编辑:正如@Evk指出的那样,我在你的问题中错过了一句"我可以这样做......"这就像上面那样.

要实际重用sineExpression,有几种可能的方法.

你可以使用Update哪个产生一个Expression基于Expression你正在使用的,与不同的孩子.这在ExpressionVisitors中大量使用.

您还可以创建一个lambda表达式并在另一个表达式中调用该lambda:

ParameterExpression x = Expression.Parameter(typeof(double), "x");
Expression two = Expression.Constant((double)2);
Expression halve = Expression.MakeBinary(ExpressionType.Divide, x, two);
Expression sine = Expression.Call(typeof(Math).GetMethod("Sin"), x);
Expression sineLambda = Expression.Lambda<Func<double, double>>(sine, x);
Expression<Func<double, double>> sineHalfLambda = Expression.Lambda<Func<double, double>>(Expression.Invoke(sineLambda, halve), x);
Func<double, double> sineHalfDelegate = sineHalfLambda.Compile();
Run Code Online (Sandbox Code Playgroud)

在这里你实际生产的不是x => Math.Sin(x/2),而是首先是一个sine,x => Math.Sin(x)然后是第二个表达式x => sine(x / 2).

从概念上讲,这意味着你有两个已编译的lambda表达式,但是编译器能够内联lambda,以便实际编译的内容x => Math.Sin(x/2)再次恢复,所以你没有两个独立编译的开销.

但更一般地说,考虑一下你的重用单位究竟是什么是值得的.如果我想产生一些表达式称为Math.Sin不同的表现形式的结果,我可能会坚持到MethodInfo那个typeof(Math).GetMethod("Sin")返回并用其作为我的可重用组件.

  • 值得在表达式语法中编写表达式`Expression <Func <double,double >> e = x => Math.Sin(x/2);`in [LinqPad](https://www.linqpad.net/ )然后调用`e.Dump()`这样它就会给你一个关于模型的视图,看看编译器是如何构建它的,虽然有时编译器可能不会手动执行第一步,而且有些东西你可以手工完成编译器不会,它会给出一些如何构建更复杂的表达式的见解. (2认同)