gpt4 book ai didi

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

转载 作者:太空狗 更新时间:2023-10-29 17:54:33 25 4
gpt4 key购买 nike

我正在尝试学习表达式,主要是为了我自己的教育。我正在尝试找出如何构建一个表达式来表示比 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 类,但显然忽略了我需要的方法。

最佳答案

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

Expression two = Expression.Constant((double)2);
Expression halve = Expression.MakeBinary(ExpressionType.Divide, x, two);

好的,此时您已经有了表示 x/2 的内容,下一步将创建 lambda 表达式 x => x/2。 (顺便说一句,您也可以使用 Expression.Divide() 而不是 MakeBinary 来更加简洁。

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

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

所以你需要做的是将每次创建 lambda 表达式之前的两点结合起来:

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

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

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

因为缩进反射(reflect)了所产生的表达式树的分支。不过,在反射(reflect)树结构的可读性优势与一般单行代码的一般可读性劣势之间存在平衡。

编辑:正如@Evk 指出的那样,我错过了您问题中的一点,即“我可以这样做......”,这与上面的内容差不多。

要实际重用 sine 表达式,有几种可能的方法。

您可以使用 Update,它根据您正在使用的 Expression 生成一个 Expression,具有不同的子级。这在 ExpressionVisitor 中大量使用。

您还可以创建一个 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();

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

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

不过,更一般地说,考虑一下您的重用单元到底是什么是值得的。如果我想在不同表达式的结果上生成多个调用 Math.Sin 的表达式,我可能会保留 typeof(Math) 的 MethodInfo。 GetMethod("Sin") 返回并将其用作我的可重用组件。

关于c# - 在构建更复杂的表达式时如何重用表达式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47928362/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com