如何评估System.Linq.Expressions.Expression

时间:2013-01-08 23:44:02

标签: c# .net

评估System.Linq.Expressions.Expression获取值(对象)的正确或可靠方法是什么?

2 个答案:

答案 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;
        }
    }
}