Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to evaluate a System.Linq.Expressions.Expression

Tags:

c#

.net

What is the correct or robust way to evaluate a System.Linq.Expressions.Expression to obtain a value (object)?

like image 562
Timothy Shields Avatar asked Jan 08 '13 23:01

Timothy Shields


People also ask

What is expression in LINQ?

In LINQ, a query expression is compiled to expression trees or to delegates, depending on the type that is applied to query result. The IEnumerable<T> type LINQ queries are compiled to delegates and IQueryable or IQueryable<T> queries are compiled to expression trees.

What LINQ expressions are used to shape results in a query?

Q. What LINQ expressions are used to shape results in a query? When the linq query is executed, the select clause specifies the type of values that will be produced. By using group clause you can group your results based on a key that you specify.

What are C# expressions?

An expression in C# is a combination of operands (variables, literals, method calls) and operators that can be evaluated to a single value. To be precise, an expression must have at least one operand but may not have any operator.

What is LINQ and lambda expressions?

The term 'Lambda expression' has derived its name from 'lambda' calculus which in turn is a mathematical notation applied for defining functions. Lambda expressions as a LINQ equation's executable part translate logic in a way at run time so it can pass on to the data source conveniently.


2 Answers

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();
}
like image 112
Timothy Shields Avatar answered Sep 23 '22 02:09

Timothy Shields


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 ParameterExpressions 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;
        }
    }
}
like image 40
John B. Lambe Avatar answered Sep 25 '22 02:09

John B. Lambe