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

时间:2017-12-21 15:48:04

标签: 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);

我还研究了如何制作一个计算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);

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

Expression sineOfHalf = Expression.Call(typeof(Math).GetMethod("Sin"), halve);

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

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

1 个答案:

答案 0 :(得分:5)

<DataGridTemplateColumn Header="Password" IsReadOnly="False" MinWidth="90">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ContentControl>
                <ContentControl.Resources>
                    <Button x:Key="btn" Content="Show"                                              
                                        Command="{Binding Path=DataContext.ShowPassword, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
                                        CommandParameter="{Binding Path=SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"/>
                </ContentControl.Resources>
                <ContentControl.Style>
                    <Style TargetType="ContentControl">
                        <Setter Property="Content" Value="{StaticResource btn}" />
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGridRow}}" Value="{x:Static CollectionView.NewItemPlaceholder}">
                                <Setter Property="Content">
                                    <Setter.Value>
                                        <TextBox />
                                    </Setter.Value>
                                </Setter>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </ContentControl.Style>
            </ContentControl>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

好的,此时你有一些代表ParameterExpression x = Expression.Parameter(typeof(double), "x"); Expression two = Expression.Constant((double)2); Expression halve = Expression.MakeBinary(ExpressionType.Divide, x, two); 的东西,你的下一步创建了lambda表达式x / 2。 (顺便提一下,您也可以使用x => x / 2而不是Expression.Divide()来更简洁。

MakeBinary

此时你有一个代表Expression sine = Expression.Call(typeof(Math).GetMethod("Sin"), x); 的表达式,你的下一步创建了lambda表达式Math.Sin(x)

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

x => Math.Sin(x)

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

Expression sine = Expression.Call(typeof(Math).GetMethod("Sin"), halve);

整个代码:

Expression.Lambda<Func<double, double>>(sine, x) // x => Math.Sin(x / 2.0)

并测试:

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

顺便提一下,直接使用Func<double, double> f = sineHalveLambda.Compile(); Console.WriteLine(f(Math.PI)); // 1 Console.WriteLine(f(0)); // 0 Console.WriteLine(f(-Math.PI)); // -1 类时,在文件中使用using static System.Linq.Expressions.Expression;通常很有用,因为您会经常使用其静态成员,然后它可以有时有助于可视化生成的树,如果你做的是单行,但有反映树的缩进:

Expression

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

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

要实际重用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); 表达式,有几种可能的方法。

您可以使用基于您正在使用的sine生成Update的{​​{1}}与不同的孩子。这在Expression s中大量使用。

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

Expression

此处您实际生成的内容不是ExpressionVisitor,而是首先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(); x => Math.Sin(x/2),然后是第二个sine表达式。

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

更一般地说,考虑一下你的重用单位究竟是什么是值得的。如果我想生成几个在不同表达式结果上调用x => sine(x / 2)的表达式,我可能会继续x => Math.Sin(x/2) Math.Sin返回并将其用作我的可重用组件。