简体   繁体   中英

How to evaluate a System.Linq.Expressions.Expression

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

I'm tentatively using the following, but don't know if it's the preferred method:

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 answer is correct for when there are no parameters. For parameterized expressions, you can use an overload of Expression.Lambda (the method used in his code) that takes a collection of ParameterExpression , but the ParameterExpression values have to be the same instances as those used in the given Expression . If it is a sub-expression of a parameterized expression using parameters from the root expression, you can get the parameters from it (pass LambdaExpression.Parameters from the root to Expression.Lambda ).

(If your expression is already a LambdaExpression , you can just cast to it and call Compile .)

Then pass your parameters to DynamicInvoke(...) .


Here's a method, extending Timothy Shields method, for invoking a sub-expression when you have the root expression. The root expression must be LamdbaExpression (or a subclass such as Expression<TDelegate> .)

Since we don't know which parameters of the root expression are required by the sub-expression, we pass all of them. (If you know this in your case, you can adapt this code.)

This doesn't handle all cases. It doesn't let you get the values of out or reference parameters, and there are probably other unsupported cases.

If your expression is not a sub-expression or you don't have the root Expression , you'll have to get the ParameterExpression s some other way.

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();
        }
    }
}

These are unit tests using the Microsoft test framework. The actual code required is three lines in the two static methods above.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM