Search code examples
vb.netlinqsumexpression-trees

call sum in expression tree


I have this query:

Dim test = result.GroupBy(Function(row) groupedindexes.Select(
                              Function(grpindex) row(grpindex)).ToArray, comp).
                      Select(Function(g) New groupedresult(g.Key, g.Sum(Function(x) Convert.ToInt32(x(3)))))

At the moment, I'm building this: g.Sum(Function(x) Convert.ToInt32(x(3)))

I have this so far:

Dim groupparameter = Expression.Parameter(GetType(Linq.IGrouping(Of Object(), Object())), "g")
Dim objectparameter = Expression.Parameter(GetType(Object()), "x")
convertMethod = GetType(System.Convert).GetMethod("ToInt32", New Type() {GetType(Object)})
Dim aggregator_expr As LambdaExpression = Expression.Lambda(expression.Call(convertMethod, Expression.ArrayAccess(objectparameter, Expression.Constant(3))), objectparameter)
Dim aggregator_func = GetType(Enumerable).GetMethods(BindingFlags.Public Or BindingFlags.Static).
Where(Function(m) m.Name = "Sum").Where(Function(m) m.ReturnType.FullName = "System.Int32").First
Dim aggregation As Expression = Expression.Call(aggregator_func, groupparameter, aggregator_expr)

At the last row, vs says:

Incorrect number of arguments supplied for call to method 'Int32 Sum(System.Collections.Generic.IEnumerable`1[System.Int32])'

Sure, there is one parameter more. But if I remove the groupparameter, I get another error message. How can I correct this?

Thanks.

EDIT:

here a simple console application:

Imports System.Reflection
Imports System.Linq.Expressions
Module Module1
    Dim groupparameter = Expression.Parameter(GetType(Linq.IGrouping(Of Object(), Object())), "g")
    Dim objectparameter = Expression.Parameter(GetType(Object()), "x")
    Dim convertMethod = GetType(System.Convert).GetMethod("ToInt32", New Type() {GetType(Object)})
    Dim aggregator_expr As LambdaExpression = Expression.Lambda(
        Expression.Call(convertMethod, Expression.ArrayAccess(objectparameter, Expression.Constant(3))), objectparameter)
    Dim aggregator_func = GetType(Enumerable).GetMethods(BindingFlags.Public Or BindingFlags.Static).
    Where(Function(m) m.Name = "Sum").Where(Function(m) m.ReturnType.FullName = "System.Int32" AndAlso m.GetParameters.Length = 2)(0).
    MakeGenericMethod(GetType(System.Func(Of Object(), Integer)))
    Sub Main()
        Dim aggregation As Expression = Expression.Call(Nothing, aggregator_func, aggregator_expr, groupparameter)
    End Sub
End Module

As you see, I changed a bit the aggregator_func. I would like to call this expression: g.Sum(Function(x) Convert.ToInt32(x(3))).

I have all together, I need only to call the variables in the right order. But I don't get it.

EDIT: The code can simply copy&paste in vs to see, what is wrong.


Solution

  • Firstly, you're looking in Enumerable for methods - that's not a good idea if you're trying to work with expression trees. You should be looking in Queryable.

    Next, you need to understand that the Sum method is overloaded - and there are multiple methods which return Int32. You need to check whether the method has two parameters. Note that you can check for multiple conditions in a single Where. For example:

    Where(Function(m) m.Name = "Sum" AndAlso
                      m.ReturnType = GetType(Integer) AndAlso
                      m.GetParameters().Length = 2)
    

    Hopefully that should at least find you the right method to call. I can't easily tell whether that's all that's wrong (a short but complete program demonstrating what you're trying to do would help) but it should at least get you closer.

    EDIT: The parameters you're using are all over the place. For example, you've got an IGrouping<object[], object[]> - that isn't an IEnumerable<Func<object[], int>> - it's an IEnumerable<object[]>. And you're currently specifying the arguments in the wrong order too - the target of an extension method is the first parameter. So here's a short but complete program that doesn't throw an exception:

    Imports System.Reflection
    Imports System.Linq.Expressions
    
    Class Test
        Shared Sub Main()
            Dim groupParameter = Expression.Parameter(GetType(Linq.IGrouping(Of Object(), Object())), "g")
            Dim objectParameter = Expression.Parameter(GetType(Object()), "x")
            Dim convertMethod = GetType(System.Convert).GetMethod("ToInt32", New Type() {GetType(Object)})
    
            Dim aggregatorExpr As LambdaExpression = Expression.Lambda(
                Expression.Call(
                    convertMethod,
                    Expression.ArrayAccess(objectParameter, Expression.Constant(3))
                ), objectParameter)
            Dim aggregatorFunc = GetType(Enumerable) _
                .GetMethods(BindingFlags.Public Or BindingFlags.Static) _
                .Where(Function(m) m.Name = "Sum") _
                .Where(Function(m) m.ReturnType.FullName = "System.Int32") _
                .Where(Function(m) m.GetParameters.Length = 2)(0) _
                .MakeGenericMethod(GetType(Object()))
            Dim aggregation As Expression = Expression.Call(
                Nothing,
                aggregatorFunc,
                groupParameter,
                aggregatorExpr)
        End Sub
    End Class