System.Linq.Expressions.Expression
评估 a以获得值(对象)的正确或稳健方法是什么?
2 回答
我暂时使用以下方法,但不知道它是否是首选方法:
public static object Evaluate(Expression e)
{
//A little optimization for constant expressions
if (e.NodeType == ExpressionType.Constant)
return ((ConstantExpression)e).Value;
return Expression.Lambda(e).Compile().DynamicInvoke();
}
当没有参数时,Timothy Shields 的答案是正确的。对于参数化表达式,您可以使用Expression.Lambda
(在他的代码中使用的方法)的重载,它采用 的集合ParameterExpression
,但ParameterExpression
值必须与给定的值相同Expression
。如果它是使用来自根表达式的参数的参数化表达式的子表达式,则可以从中获取参数(LambdaExpression.Parameters
从根传递到Expression.Lambda
)。
(如果您的表达式已经是 a LambdaExpression
,您可以直接转换为它并调用Compile
。)
然后将参数传递给DynamicInvoke(...)
.
这是一种扩展 Timothy Shields 方法的方法,用于在您拥有根表达式时调用子表达式。根表达式必须是LamdbaExpression
(或子类,例如Expression<TDelegate>
.)
由于我们不知道子表达式需要根表达式的哪些参数,所以我们将它们全部传递。(如果您知道这一点,您可以修改此代码。)
这并不能处理所有情况。它不允许您获取out
或引用参数的值,并且可能还有其他不受支持的情况。
如果您的表达式不是子表达式或者您没有 root Expression
,则必须以ParameterExpression
其他方式获取 s 。
using System;
using System.Linq;
using System.Linq.Expressions;
namespace LinqTest
{
public class LinqCompileSubExpression
{
/// <summary>
/// Compile and invoke a sub-expression of a <see cref="LambdaExpression"/>.
/// </summary>
/// <param name="rootExpression">A parameterised expression.</param>
/// <param name="subExpression">An sub-expression or <paramref name="rootExpression"/>.</param>
/// <param name="arguments">
/// The arguments to be supplied on invoking. These must match the parameters to the root expression (empty if it has no parameters).
/// Any parameters not used by the sub-expression are ignored.
/// </param>
/// <returns>The return value of the sub-expression.</returns>
/// <typeparam name="TReturn">The type of the return value. Use <see cref="Object"/> if it is not known.</typeparam>
/// <remarks>
/// If invoking the same expression multiple times, use <see cref="CompileSubExpression(LambdaExpression, Expression)"/> once,
/// then invoke the delegate (for efficiency).
/// </remarks>
public static TReturn InvokeSubExpression<TReturn>(LambdaExpression rootExpression, Expression subExpression, params object[] arguments)
{
// compile it (to a delegate):
Delegate compiledDelegate = CompileSubExpression(rootExpression, subExpression);
// invoke the delegate:
return (TReturn)compiledDelegate.DynamicInvoke(arguments);
}
/// <summary>
/// Compile a sub-expression of a <see cref="LambdaExpression"/>.
/// </summary>
/// <param name="rootExpression">A parameterised expression.</param>
/// <param name="subExpression">An sub-expression or <paramref name="rootExpression"/>.</param>
/// <returns>The compiled expression.</returns>
public static Delegate CompileSubExpression(LambdaExpression rootExpression, Expression subExpression)
{
// convert the sub-expression to a LambdaExpression with the same parameters as the root expression:
LambdaExpression lambda = Expression.Lambda(subExpression, rootExpression.Parameters);
// compile it (to a delegate):
return lambda.Compile();
}
}
}
这些是使用 Microsoft 测试框架的单元测试。实际需要的代码是上面两个静态方法中的三行代码。
using System;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using static LinqTest.LinqCompileSubExpression;
namespace LinqTest
{
[TestClass]
public class LinqCompileSubExpressionTest
{
[TestMethod]
public void InvokeExpressionTest1()
{
Expression<Func<string, int>> rootExpression = s => s.Substring(4).Length;
var subExpression = ((MemberExpression)rootExpression.Body).Expression; // just the `s.Substring(4)` part
Assert.AreEqual("t string", InvokeSubExpression<string>(rootExpression, subExpression, "input string"));
}
[TestMethod]
public void InvokeExpressionTest2()
{
Expression<Func<object, int>> rootExpression = x => x.ToString().Length;
var subExpression = ((MemberExpression)rootExpression.Body).Expression; // just the `x.ToString()` part
Assert.AreEqual("5", InvokeSubExpression<string>(rootExpression, subExpression, 5));
}
[TestMethod]
public void InvokeExpressionTest3()
{
Expression<Func<ClassForTest, int>> rootExpression = x => x.StrProperty.Length + 15;
var subExpression = ((BinaryExpression)rootExpression.Body).Right; // `15`
Assert.AreEqual(15, InvokeSubExpression<int>(rootExpression, subExpression, new ClassForTest())); // argument is irrelevant
}
[TestMethod]
public void InvokeExpressionTest4()
{
Expression<Func<int, int>> rootExpression = x => Math.Abs(x) + ClassForTest.GetLength(x.ToString());
var subExpression = ((BinaryExpression)rootExpression.Body).Right;
Assert.AreEqual(3, InvokeSubExpression<int>(rootExpression, subExpression, 123)); // we pass root parameter but evaluate the sub-expression only
}
[TestMethod]
public void InvokeExpressionTest5()
{
Expression<Func<int, int>> rootExpression = x => ClassForTest.GetLength(x.ToString());
var subExpression = ((MethodCallExpression)rootExpression.Body).Arguments[0]; // just the `x.ToString()` part
Assert.AreEqual("123", InvokeSubExpression<string>(rootExpression, subExpression, 123)); // we pass root parameter but evaluate the sub-expression only
}
public class ClassForTest
{
public string StrProperty { get; set; }
public static int GetLength(string s) => s.Length;
}
}
}