评估System.Linq.Expressions.Expression
获取值(对象)的正确或可靠方法是什么?
答案 0 :(得分:15)
我暂时使用以下内容,但不知道它是否是首选方法:
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();
}
答案 1 :(得分:1)
Timothy Shields在没有参数时回答是正确的。
对于参数化表达式,您可以使用Expression.Lambda
(在其代码中使用的方法)的重载,该重载采用ParameterExpression
的集合,但ParameterExpression
值必须与那些相同的实例在给定的Expression
中使用。如果它是使用根表达式中的参数的参数化表达式的子表达式,则可以从中获取参数(将LambdaExpression.Parameters
从根传递到Expression.Lambda
)。
(如果您的表达式已经是LambdaExpression
,您可以直接转换为Compile
。)
然后将参数传递给DynamicInvoke(...)
。
这是一个扩展Timothy Shields方法的方法,用于在拥有根表达式时调用子表达式。
根表达式必须是LamdbaExpression
(或者是Expression<TDelegate>
等子类。)
由于我们不知道子表达式需要根表达式的哪些参数,因此我们传递了所有这些参数。 (如果您知道这种情况,可以调整此代码。)
这并不能处理所有情况。
它不会让您获得out
或参考参数的值,并且可能还有其他不受支持的案例。
如果您的表达不是子表达式,或者您没有根Expression
,那么您必须以其他方式获取ParameterExpression
。
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;
}
}
}