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; 通常很有用,因为您会经常使用它的静态成员,然后它有时 如果您将其作为单行但缩进反映树,则有助于可视化生成的树:
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);
因为缩进反映了生成的表达式树的分支。虽然在反映树结构的可读性优势与一般单行代码的一般可读性劣势之间存在平衡。
编辑:正如@Evk 指出的那样,我错过了你的问题中说“我可以像这样......”这与上面差不多。
要实际重用sine 表达式,有几种可能的方法。
您可以使用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();
您实际生成的不是x => Math.Sin(x/2),而是首先是sine,即x => Math.Sin(x),然后是第二个表达式,即x => sine(x / 2)。
从概念上讲,这意味着您有两个已编译的 lambda 表达式,但编译器能够内联内部 lambda,以便实际编译的内容再次恢复为 x => Math.Sin(x/2),因此您没有开销两个单独的编译。
不过,更一般地说,考虑一下您的重用单元到底是什么是值得的。如果我想针对不同表达式的结果生成多个调用 Math.Sin 的表达式,我可能会保留 typeof(Math).GetMethod("Sin") 返回的 MethodInfo 并将其用作我的可重用组件。