Search code examples
c#linqgenericsexpression-trees

Unable to manually create Lambda expression tree with List<T>


I need help creating this lambda expression manually. I cannot get past properly creating MemberExpression that isolates the "Breed" property of the underlying List<Dog>. Breed is a property of Dog in this example.

This is the lambda I need to create manually:

int maxlen = dogList.Select(d => d.Breed.Trim().Length).OrderByDescending(d1 => d1).First();

Here is my attempt at trying to get the Breed property:

ParameterExpression dogParam = Expression.Parameter(typeof(List<Dog>), "dog");

MemberExpression dogMember = 
    Expression.Field(dogParam, dogMember.Type.GetGenericTypeDefinition().GetProperty("Breed"));

Solution

  • It is a pain, a real pain.

    First notice that you can cheat, and take a look at what the compiler generates:

    Expression<Func<List<Dog>, int>> exp = dogList => dogList.Select(d => d.Breed.Trim().Length).OrderByDescending(d1 => d1).First();
    

    Sadly this isn't really a big help... You have two "inner" lambda expressions (the Select and the OrderByDescending) and three generic methods (Select, OrderByDescending, First)... A pain.

    I've commented all the lines of code, so it should be pretty clear what everything does.

    // The dogList parameter
    ParameterExpression dogParam = Expression.Parameter(typeof(List<Dog>), "dog");
    
    // Begin of inner Select expression
    
    // d parameter
    ParameterExpression dParam = Expression.Parameter(typeof(Dog), "d");
    
    // d.Breed *property* access (if it is a *field*, use Expression.Field)
    MemberExpression dogProperty = Expression.Property(dParam, nameof(Dog.Breed));
    
    // d.Breed.Trim() method call
    MethodCallExpression trimCall = Expression.Call(dogProperty, nameof(string.Trim), Type.EmptyTypes);
    
    // d.Breed.Trim().Length property access
    MemberExpression lengthProperty = Expression.Property(trimCall, nameof(string.Length));
    
    // d => d.Breed.Trim().Length
    Expression<Func<Dog, int>> selectExpression = Expression.Lambda<Func<Dog, int>>(lengthProperty, dParam);
    
    // End of inner Select expression
    
    // Find the "right" overload of Enumerable.Select... A pain.
    MethodInfo selectTSourceTResult = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                                        where x.Name == nameof(Enumerable.Select)
                                        let args = x.GetGenericArguments()
                                        where args.Length == 2
                                        let pars = x.GetParameters()
                                        where pars.Length == 2 &&
                                            pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
                                            pars[1].ParameterType == typeof(Func<,>).MakeGenericType(args[0], args[1])
                                        select x).Single();
    
    // Make the generic overload of Enumerable.Select "specific" for using with Func<Dog, int>
    MethodInfo selectDogInt32 = selectTSourceTResult.MakeGenericMethod(typeof(Dog), typeof(int));
    
    // Note that Enumerable.Select is a static method, the first parameter is the IEnumerable, the second is the Func<,>
    // Enumerable.Select(dogList, d => d.Breed.Trim().Length)
    MethodCallExpression selectCall = Expression.Call(selectDogInt32, dogParam, selectExpression);
    
    // Begin of inner OrderByDescending expression
    
    // d1 parameter
    ParameterExpression d1Param = Expression.Parameter(typeof(int), "d1");
    
    // d1 => d1
    Expression<Func<int, int>> orderByExpression = Expression.Lambda<Func<int, int>>(d1Param, d1Param);
    
    // End of inner OrderByDescending expression
    
    // Find the "right" overload of Enumerable.OrderByDescending... Another pain.
    MethodInfo orderByDescendingTSourceTKey = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                                                where x.Name == nameof(Enumerable.OrderByDescending)
                                                let args = x.GetGenericArguments()
                                                where args.Length == 2
                                                let pars = x.GetParameters()
                                                where pars.Length == 2 &&
                                                    pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
                                                    pars[1].ParameterType == typeof(Func<,>).MakeGenericType(args[0], args[1])
                                                select x).Single();
    
    // Make the generic overload of Enumerable.OrderByDescending "specific" for using with Func<int, int>
    MethodInfo orderByDescendingInt32Int32 = orderByDescendingTSourceTKey.MakeGenericMethod(typeof(int), typeof(int));
    
    // Note that Enumerable.OrderByDescending is a static method, the first parameter is the IEnumerable, the second is the Func<,>
    // Enumerable.OrderByDescending(Enumerable.Select(...), d1 => d1)
    MethodCallExpression orderByDescendingCall = Expression.Call(orderByDescendingInt32Int32, selectCall, orderByExpression);
    
    // Find the "right" overload of Enumerable.First... Third big pain.
    MethodInfo firstTSource = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                                where x.Name == nameof(Enumerable.First)
                                let args = x.GetGenericArguments()
                                where args.Length == 1
                                let pars = x.GetParameters()
                                where pars.Length == 1 &&
                                    pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0])
                                select x).Single();
    
    // Make the generic overload of Enumerable.OrderByDescending "specific" for using with int
    MethodInfo firstInt32 = firstTSource.MakeGenericMethod(typeof(int));
    
    // Note that Enumerable.First is a static method, the first parameter is the IEnumerable
    // Enumerable.First(Enumerable.OrderByDescending(...))
    MethodCallExpression firstCall = Expression.Call(firstInt32, orderByDescendingCall);