4

When building a LambdaExpression at runtime, if I use a LambdaExpression as a parameter to a call expression (like when using Linq), then compile the main lambda, does the nested lambda also compile or does it need to?

The code functions the same if I use the LambdaExpression as a parameter to a method taking Func<T, T2> or if I compile it and use a Expression.Constant over the compiled Func.

Not compiled:

    var selectParam = Expression.Parameter(propType, "selectParam");
    var selectExp = Expression.Call(typeof(System.Linq.Enumerable).GetMethods().First(a => a.Name == "Select" && /*Func<TSource,TResult>*/ a.GetParameters().Last().ParameterType.GenericTypeArguments.Length == 2).MakeGenericMethod(propType, typeof(int)),
                                    whereExp,
                                    Expression.Lambda(Expression.Property(selectParam, "Length"), selectParam));

Compiled:

    var selectParam = Expression.Parameter(propType, "selectParam");
    var selectExp = Expression.Call(typeof(System.Linq.Enumerable).GetMethods().First(a => a.Name == "Select" && /*Func<TSource,TResult>*/ a.GetParameters().Last().ParameterType.GenericTypeArguments.Length == 2).MakeGenericMethod(propType, typeof(int)),
                                    whereExp,
                                    Expression.Constant(Expression.Lambda(Expression.Property(selectParam, "Length"), selectParam).Compile())); //compile

The expressions I'm building are called millions of times in a loop so I'd like to know if compiling the outer lambda compiles the inner lambdas properly.

Since this isn't easy to explain, see my fiddle here.

I'm pretty sure they won't be compiled as the methods being called could want them as Expressions to parse them. In this case, is there a runtime performance gain to compile them when used like this?

Thinking at a higher level, When used in a standard way in a loop - is this optimized at all? Certainly they aren't compiled on every call when doing linq over an array or such?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
TheSoftwareJedi
  • 34,421
  • 21
  • 109
  • 151

1 Answers1

4

Short answer: yes, each inner lambda will be compiled.


I slightly modified your first method (but it generates same expression):

private static Expression<Func<int>> ActuallyInnerAlsoCompile()
{
    var strType = typeof(string);
    var intType = typeof(int);
    var enumearbleType = typeof(Enumerable);

    var array = Expression.NewArrayInit(strType, Expression.Constant("test"), Expression.Constant("test2"));

    var x = Expression.Parameter(strType, "whereParam");
    var whereExp = Expression.Call(enumearbleType,
        "Where",
        new[] {strType},
        array,
        Expression.Lambda(Expression.NotEqual(Expression.PropertyOrField(x, "Length"), Expression.Constant(4)), x));

    var selectExp = Expression.Call(enumearbleType,
        "Select",
        new[] {strType, intType},
        whereExp,
        Expression.Lambda(Expression.PropertyOrField(x, "Length"), x));

    var firstOrDefault = Expression.Call(enumearbleType,
        "FirstOrDefault",
        new[] {intType},
        selectExp);

    return Expression.Lambda<Func<int>>(firstOrDefault);
}

Now you can refer to this answer and compile your expression into new assembly:

var lambda = ActuallyInnerAlsoCompile();

var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
    new AssemblyName("dynamicAssembly"),
    AssemblyBuilderAccess.Save);

var dm = dynamicAssembly.DefineDynamicModule("dynamicModule", "dynamic.dll");
var dt = dm.DefineType("dynamicType");
var m1 = dt.DefineMethod(
    "dynamicMethod",
    MethodAttributes.Public | MethodAttributes.Static);

lambda.CompileToMethod(m1);
dt.CreateType();

dynamicAssembly.Save("dynamic.dll");

If you open that dynamic.dll with some IL tool (dotPeek for example) you will see something like:

// Decompiled with JetBrains decompiler
// Type: dynamicType
// Assembly: dynamicAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 94346EDD-3BCD-4EB8-BA4E-C25343918535

using System;
using System.Collections.Generic;
using System.Linq;

internal class dynamicType
{
  public static int dynamicMethod()
  {
    return ((IEnumerable<string>) new string[2]
    {
      "test",
      "test2"
    }).Where<string>(new Func<string, bool>(dynamicType.\u003CExpressionCompilerImplementationDetails\u003E\u007B1\u007Dlambda_method)).Select<string, int>(new Func<string, int>(dynamicType.\u003CExpressionCompilerImplementationDetails\u003E\u007B2\u007Dlambda_method)).FirstOrDefault<int>();
  }

  private static bool \u003CExpressionCompilerImplementationDetails\u003E\u007B1\u007Dlambda_method(string whereParam)
  {
    return whereParam.Length != 4;
  }

  private static int \u003CExpressionCompilerImplementationDetails\u003E\u007B2\u007Dlambda_method(string whereParam)
  {
    return whereParam.Length;
  }
}

Or (without ugly unicode escape sequences)

Aleks Andreev
  • 7,016
  • 8
  • 29
  • 37
  • If this is the case and inner lambdas are always compiled, how would you pass the lambda in as an Expression instead of a Func? For a QueryProvider to parse? – TheSoftwareJedi Feb 12 '19 at 16:19
  • @TheSoftwareJedi I'm not sure I've understand your comment. Do you want to generate an expression and pass it to the QueryProvider? If so you do not need a `Compile` call at all. Could you please edit (or create a new one) question and explain (with examples) what you trying to achive? – Aleks Andreev Feb 12 '19 at 18:31
  • I simply meant if the call were to Queryable.Where instead of Enumerable.Where. And "Compile" was not called on the inner lambdas (like above). I would think at that point the method takes an Expression so they are not converted to Func automatically. – TheSoftwareJedi Feb 12 '19 at 18:37