Why would you quote a LambdaExpression?
Asked Answered
I

1

7

I've read this answer and understood from it the specific case it highlights, which is when you have a lambda inside another lambda and you don't want to accidentally have the inner lambda also compile with the outer one. When the outer one is compiled, you want the inner lambda expression to remain an expression tree. There, yes, it makes sense quoting the inner lambda expression.

But that's about it, I believe. Is there any other use case for quoting a lambda expression?

And if there isn't, why do all the LINQ operators, i.e. the extensions on IQueryable<T> that are declared in the Queryable class quote the predicates or lambdas they receive as arguments when they package that information in the MethodCallExpression.

I tried an example (and a few others over the last couple of days) and it doesn't seem to make any sense to quote a lambda in this case.

Here's a method call expression to a method that expects a lambda expression (and not a delegate instance) as its only parameter.

I then compile the MethodCallExpression by wrapping it inside a lambda.

But that doesn't compile the inner LambdaExpression (the argument to the GimmeExpression method) as well. It leaves the inner lambda expression as an expression tree and does not make a delegate instance of it.

In fact, it works well without quoting it.

And if I do quote the argument, it breaks and gives me an error indicating that I am passing in the wrong type of argument to the GimmeExpression method.

What's the deal? What's this quoting all about?

private static void TestMethodCallCompilation()
{
    var methodInfo = typeof(Program).GetMethod("GimmeExpression", 
        BindingFlags.NonPublic | BindingFlags.Static);

    var lambdaExpression = Expression.Lambda<Func<bool>>(Expression.Constant(true));

    var methodCallExpression = Expression.Call(null, methodInfo, lambdaExpression);

    var wrapperLambda = Expression.Lambda(methodCallExpression);
    wrapperLambda.Compile().DynamicInvoke();
}

private static void GimmeExpression(Expression<Func<bool>> exp)
{
    Console.WriteLine(exp.GetType());
    Console.WriteLine("Compiling and executing expression...");
    Console.WriteLine(exp.Compile().Invoke());
}
Inutile answered 7/5, 2015 at 15:14 Comment(0)
C
5

You have to pass the argument as a ConstantExpression:

private static void TestMethodCallCompilation()
{
    var methodInfo = typeof(Program).GetMethod("GimmeExpression", 
        BindingFlags.NonPublic | BindingFlags.Static);

    var lambdaExpression = Expression.Lambda<Func<bool>>(Expression.Constant(true));

    var methodCallExpression = 
      Expression.Call(null, methodInfo, Expression.Constant(lambdaExpression));

    var wrapperLambda = Expression.Lambda(methodCallExpression);
    wrapperLambda.Compile().DynamicInvoke();
}

private static void GimmeExpression(Expression<Func<bool>> exp)
{
    Console.WriteLine(exp.GetType());
    Console.WriteLine("Compiling and executing expression...");
    Console.WriteLine(exp.Compile().Invoke());
}

The reason should be pretty obvious - you're passing a constant value, so it has to be a ConstantExpression. By passing the expression directly, you're explicitly saying "and get the value of exp from this complicated expression tree". And since that expression tree doesn't actually return a value of Expression<Func<bool>>, you get an error.

The way IQueryable works doesn't really have much to do with this. The extension methods on IQueryable have to preserve all information about the expressions - including the types and references of the ParameterExpressions and similar. This is because they don't actually do anything - they just build the expression tree. The real work happens when you call queryable.Provider.Execute(expression). Basically, this is how the polymorphism is preserved even though we're doing composition, rather than inheritance (/interface implementation). But it does mean that the IQueryable extension methods themselves cannot do any shortcuts - they don't know anything about the way the IQueryProvider is actually going to interpret the query, so they can't throw anything away.

The most important benefit you get from this, though, is that you can compose the queries and subqueries. Consider a query like this:

from item in dataSource
where item.SomeRelatedItem.Where(subItem => subItem.SomeValue == 42).Count() > 2
select item;

Now, this is translated to something like this:

dataSource.Where(item => item.SomeRelatedItem.Where(subItem => subItem.SomeValue == 42).Count() > 2);

The outer query is pretty obvious - we'll get a Where with the given predicate. The inner query, however, is actually going to be a Call to Where, taking the actual predicate as an argument.

By making sure that actual invocations of the Where method are actually translated into a Call of the Where method, both of these cases become the same, and your LINQProvider is that one bit simpler :)

I've actually written LINQ providers that don't implement IQueryable, and which actually have some useful logic in the methods like Where. It's a lot simpler and more efficient, but has the drawback described above - the only way to handle subqueries would be to manually Invoke the Call expressions to get the "real" predicate expression. Yikes - that's quite an overhead for a simple LINQ query!

And of course, it helps you compose different queryable providers, although I haven't actually seen (m)any examples of using two completely different providers in a single query.

As for the difference between Expression.Constant and Expression.Quote themselves, they seem rather similar. The crucial difference is that Expression.Constant will treat any closures as actual constants, rather than closures. Expression.Quote on the other hand, will preserve the "closure-ness" of the closures. Why? Because the closure objects themselves are also passed as Expression.Constant :) And since IQueryable trees are doing lambdas of lambdas of lambdas of [...], you really don't want to lose the closure semantics at any point.

Camey answered 7/5, 2015 at 15:58 Comment(1)
Thank you very much. I had been at this for several months now. It finally clicked after doing lots of examples and thinking a lot over the last few months about this. I had some theories about the why and some of them were right. Your answer also helped me.Inutile

© 2022 - 2024 — McMap. All rights reserved.