THX-1138
THX-1138

Reputation: 21750

Find a method using parameter types where parameters are generic

I am sure this is a duplicate, but I can not find the answer.

System.Linq.Queryable has a method with following signature:

public static int Count<TSource>(
    this IQueryable<TSource> source, 
    Expression<Func<TSource, bool>> predicate);

How do I use System.Type::GetMethod method to get this method?

typeof(System.Linq.Queryable).GetMethod("Count", type[]{ ??? });

Upvotes: 2

Views: 405

Answers (2)

Mike Strobel
Mike Strobel

Reputation: 25623

The method with the least effort would probably be to capture the method you want with an expression tree, then delve in to find the method reference. You can do this once to get the generic method definition, then cache the result and reuse it as necessary.

Expression<Func<IQueryable<int>, int>> getCount = p => p.Count();
MethodInfo countMethod = ((MethodCallExpression)getCount.Body).Method.GetGenericMethodDefinition();

This has the benefit of being somewhat resilient to API changes (e.g., the addition of more "Count" members); the method returned will be whatever method your expression binds to at compile time.

You could extract this behavior out into utility methods if you like:

public static MethodInfo MethodOf(Expression<Action> accessExpression, bool dropTypeArguments = false)
{
    if (accessExpression == null)
        throw new ArgumentNullException("accessExpression");

    var callExpression = accessExpression.Body as MethodCallExpression;
    if (callExpression == null)
        throw new ArgumentException("Expression body must be a method call.", "accessExpression");

    var method = callExpression.Method;

    if (dropTypeArguments && method.IsGenericMethod)
        return method.GetGenericMethodDefinition();

    return method;
}

public static MethodInfo MethodOf<TInstance>(Expression<Action<TInstance>> call, bool dropTypeArguments = false)
{
    if (call == null)
        throw new ArgumentNullException("call");

    var callExpression = call.Body as MethodCallExpression;
    if (callExpression == null)
        throw new ArgumentException("Expression body must be a method call.", "call");

    var method = callExpression.Method;

    if (dropTypeArguments && method.IsGenericMethod)
        return method.GetGenericMethodDefinition();

    return method;
}

Use the first overload for static-style invocations and the latter for instance-style invocations, e.g.:

var countMethod1 = Extensions.MethodOf(() => Queryable.Count(default(IQueryable<int>)), dropTypeArguments: true);
var countMethod2 = Extensions.MethodOf((IQueryable<int> p) => p.Count(), dropTypeArguments: true);

To preserve the type arguments (e.g., to resolve Count<int>() instead of Count<T>()) just omit the dropTypeArguments: true argument or set it to false.

Note that these aren't terribly comprehensive; they will not, for example, drop generic parameters on the declaring type (only on the method itself). Feel free to use, extend, or throw away :).

Upvotes: 1

pil0t
pil0t

Reputation: 2183

You could use

var method = typeof (System.Linq.Queryable).GetMethods().Single(a=>a.Name == "Count" && a.GetParameters().Length == 2);

And after, if you want to invoke it

method.MakeGenericMethod(typeof (YourType));

For additional, it could be filtered (for different selects):

var sel1 = typeof (Queryable).GetMethods().Single(a => a.Name == "Select"
                && a.MakeGenericMethod(typeof(object), typeof(object)).GetParameters()[1].ParameterType == typeof(Expression<Func<object, object>>));

var sel2 = typeof(Queryable).GetMethods().Single(a => a.Name == "Select"
                && a.MakeGenericMethod(typeof(object), typeof(object)).GetParameters()[1].ParameterType == typeof(Expression<Func<object,int, object>>));

Upvotes: 1

Related Questions