18

System.Linq.Expressions.Expression评估 a以获得值(对象)的正确或稳健方法是什么?

4

2 回答 2

16

我暂时使用以下方法,但不知道它是否是首选方法:

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();
}
于 2013-01-08T23:44:02.147 回答
2

当没有参数时,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;
        }
    }
}
于 2018-04-08T03:06:47.190 回答