Search code examples
c#lambdasumexpression

What is the way to create an expression to get sum of single property of c# list?


I am trying to create an expression for one list property, but facing an error as below.

Error: Incorrect number of arguments supplied for call to method Sum

I tried bellow code

ParameterExpression argParam = Expression.Parameter(typeof(T), "s");

property1 = typeof(T).GetProperty(nameof(Srt));
property2 = property1.PropertyType.GetProperty(Srt.Srtd));
propertyExp1 = Expression.Property(argParam, property1);
propertyExp = Expression.Property(propertyExp1, property2);

// in propertyExp I got {s.Srt.Srtd}

ParameterExpression argX = Expression.Parameter(typeof(Srtd), "x");
var prt1= typeof(Srtd).GetProperty(nameof(Srtd.Elt));
var prtExp= Expression.Property(argX, prt1);

// in prtExp I got {x.Elt}

var method = typeof(System.Linq.Enumerable).GetMethod("Sum", new[] { typeof(IEnumerable<decimal?>) });
var exp2 = Expression.Call(method, propertyExp, prtExp);

In Expression.Call I am facing the error "Incorrect number of arguments supplied for call to method Sum".

I want expression like { s.Srt.Srtd.Sum(x=>x.Elt) }, Can someone help me for this?


Solution

  • Note that Sum in s.Srt.Srtd.Sum(x=>x.Elt) is an extension method having a hidden first this parameter and a second selector parameter:

    public static decimal? Sum<TSource> (
        this System.Collections.Generic.IEnumerable<TSource> source,
        Func<TSource,decimal?> selector);
    

    Therefore, we would have to provide two type arguments:

    method = typeof(System.Linq.Enumerable)
        .GetMethod("Sum", new[] {
            typeof(IEnumerable<Srtd>),
            typeof(Func<Srtd,decimal?>)
         });
    

    However, the problem here is that the TSource type parameter of the method as it is declared in Enumerable is open. It is possible to write typeof(IEnumerable<>), but not typeof(Func<,decimal?>) for the selector with only one of the two parameters open.

    Instead, I will be using GetMethods() and filter the right method in a LINQ query in the example below. What remains to do is to specify the open generic type parameter with MakeGenericMethod.

    I used this setup (as specified in one of your comments, but made the decimal nullable as in your question):

    class T { public A a { get; set; } }
    class A { public List<B> b { get; set; } }
    class B { public decimal? c { get; set; } }
    

    Note that you have to make a Lambda expression for x => x.c. I don't see this in your attempt.

    // Parameter t in: t => t.a.b.Sum(x => x.c)
    ParameterExpression paramT = Expression.Parameter(typeof(T), "t");
    
    // Property a in: t.a
    PropertyInfo propTa = typeof(T).GetProperty(nameof(T.a));
    
    // Property b in: a.b
    PropertyInfo propAb = typeof(A).GetProperty(nameof(A.b));
    
    // Expression: t.a 
    MemberExpression taExpr = Expression.Property(paramT, propTa);
    
    // Expression: t.a.b
    MemberExpression tabExpr = Expression.Property(taExpr, propAb);
    
    // Parameter x in: x => x.c
    ParameterExpression paramX = Expression.Parameter(typeof(B), "x");
    
    // Property c in: x.c where x is of type B
    PropertyInfo propBc = typeof(B).GetProperty(nameof(B.c));
    
    // Expression: x.c
    MemberExpression xcExpr = Expression.Property(paramX, propBc);
    
    // Method:  public static decimal? Sum<B>(this IEnumerable<B> source, Func<B, decimal?> selector)
    MethodInfo sumMethod = typeof(System.Linq.Enumerable).GetMethods()
        .Where(m => m.Name == "Sum" && 
                    m.ReturnType == typeof(decimal?) &&
                    m.GetParameters().Length == 2)
        .First()
        .MakeGenericMethod(typeof(B));
    
    // Lambda expression: x => x.c
    LambdaExpression lambdaXToXc = Expression.Lambda(xcExpr, paramX);
    
    // Call expression: t.a.b.Sum(x => x.c). Instance is null because method is static
    MethodCallExpression sumCallExpr = Expression.Call(instance: null, method: sumMethod, tabExpr, lambdaXToXc);