Rushi Sharma
Rushi Sharma

Reputation: 108

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?

Upvotes: 0

Views: 326

Answers (1)

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112324

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);

Upvotes: 2

Related Questions