Select Right Generic Method with Reflection

18,035

Solution 1

It can be done, but it's not pretty!

For example, to get the first overload of Where mentioned in your question you could do this:

var where1 = typeof(Queryable).GetMethods()
                 .Where(x => x.Name == "Where")
                 .Select(x => new { M = x, P = x.GetParameters() })
                 .Where(x => x.P.Length == 2
                             && x.P[0].ParameterType.IsGenericType
                             && x.P[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
                             && x.P[1].ParameterType.IsGenericType
                             && x.P[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>))
                 .Select(x => new { x.M, A = x.P[1].ParameterType.GetGenericArguments() })
                 .Where(x => x.A[0].IsGenericType
                             && x.A[0].GetGenericTypeDefinition() == typeof(Func<,>))
                 .Select(x => new { x.M, A = x.A[0].GetGenericArguments() })
                 .Where(x => x.A[0].IsGenericParameter
                             && x.A[1] == typeof(bool))
                 .Select(x => x.M)
                 .SingleOrDefault();

Or if you wanted the second overload:

var where2 = typeof(Queryable).GetMethods()
                 .Where(x => x.Name == "Where")
                 .Select(x => new { M = x, P = x.GetParameters() })
                 .Where(x => x.P.Length == 2
                             && x.P[0].ParameterType.IsGenericType
                             && x.P[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
                             && x.P[1].ParameterType.IsGenericType
                             && x.P[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>))
                 .Select(x => new { x.M, A = x.P[1].ParameterType.GetGenericArguments() })
                 .Where(x => x.A[0].IsGenericType
                             && x.A[0].GetGenericTypeDefinition() == typeof(Func<,,>))
                 .Select(x => new { x.M, A = x.A[0].GetGenericArguments() })
                 .Where(x => x.A[0].IsGenericParameter
                             && x.A[1] == typeof(int)
                             && x.A[2] == typeof(bool))
                 .Select(x => x.M)
                 .SingleOrDefault();

Solution 2

You can somewhat elegantly select a specific generic overload of a method at compile-time, without passing any strings to run-time searches like the other answers here do.

Static Methods

Suppose you have multiple static methods of the same name like:

public static void DoSomething<TModel>(TModel model)

public static void DoSomething<TViewModel, TModel>(TViewModel viewModel, TModel model)

// etc

If you create an Action or Func that matches the generic count and parameter count of the overload you're looking for, you can select it at compile-time with relatively few acrobatics.

Example: Select the first method - returns void, so use an Action, takes one generic. We use object to avoid specifying type just yet:

var method = new Action<object>(MyClass.DoSomething<object>);

Example: Select the second method - returns void, so Action, 2 generic types so use type object twice, once for each of the 2 generic parameters:

var method = new Action<object, object>(MyClass.DoSomething<object, object>);

You just got the method you wanted without doing any crazy plumbing, and no run-time searching or usage of risky strings.

MethodInfo

Typically in Reflection you want the MethodInfo object, which you can also get in a compile-safe way. This is when you pass the actual generic types you want to use in your method. Assuming you wanted the second method above:

var methodInfo = method.Method.MakeGenericMethod(type1, type2);

There's your generic method without any of the reflection searching or calls to GetMethod(), or flimsy strings.

Static Extension Methods

The specific example you cite with Queryable.Where overloads forces you to get a little fancy in the Func definition, but generally follows the same pattern. The signature for the most commonly used Where() extension method is:

public static IQueryable<TModel> Where<TModel>(this IQueryable<TModel>, Expression<Func<TModel, bool>>)

Obviously this will be slightly more complicated - here it is:

var method = new Func<IQueryable<object>,
                      Expression<Func<object, bool>>,
                      IQueryable<object>>(Queryable.Where<object>);

var methodInfo = method.Method.MakeGenericMethod(modelType);

Instance Methods

Incorporating Valerie's comment - to get an instance method, you'll need to do something very similar. Suppose you had this instance method in your class:

public void MyMethod<T1>(T1 thing)

First select the method the same way as for statics:

var method = new Action<object>(MyMethod<object>);

Then call GetGenericMethodDefinition() to get to the generic MethodInfo, and finally pass your type(s) with MakeGenericMethod():

var methodInfo = method.Method.GetGenericMethodDefinition().MakeGenericMethod(type1);

Decoupling MethodInfo and Parameter Types

This wasn't requested in the question, but once you do the above you may find yourself selecting the method in one place, and deciding what types to pass it in another. You can decouple those 2 steps.

If you're uncertain of the generic type parameters you're going to pass in, you can always acquire the MethodInfo object without them.

Static:

var methodInfo = method.Method;

Instance:

var methodInfo = method.Method.GetGenericMethodDefinition();

And pass that on to some other method that knows the types it wants to instantiate and call the method with - for example:

processCollection(methodInfo, type2);

...

protected void processCollection(MethodInfo method, Type type2)
{
    var type1 = typeof(MyDataClass);
    object output = method.MakeGenericMethod(type1, type2).Invoke(null, new object[] { collection });
}

One thing this especially helps with is selecting a specific instance method of a class, from inside the class, then exposing that to outside callers who need it with various types later on.

Addendum

A number of comments below say they cannot get this to work. It might not be surprising that I don't often have to select a generic method like this, but I happen to be doing so today, in well-tested code used behind the scenes all the time, so I thought I'd provide that real-world example - and perhaps it will help those who struggle to get this to work.

C# lacks a Clone method, so we have our own. It can take a number of arguments, including those that explain how to recursively copy IEnumerable properties inside the source object.

The method that copies an IEnumerable is named CopyList, and looks like this:

public static IEnumerable<TTo> CopyList<TTo>(
    IEnumerable<object> from,
    Func<PropertyInfo, bool> whereProps,
    Dictionary<Type, Type> typeMap
)
    where TTo : new()
{

To complicate things (and flex the muscles of this approach), it has several overloads, like this one:

public static IEnumerable<TTo> CopyList<TTo>(
    IEnumerable<object> from,
    Dictionary<Type, Type> typeMap
)
    where TTo : new()
{

So, we've got several (I'm only showing you 2, but there are more in the code) method signatures. They have the same number of Generic arguments, but a different number of method arguments. The names are identical. How are we possibly going to call the right method? Begin the C# ninjaing!

var listTo = ReflectionHelper.GetIEnumerableType(
    fromValue.GetType());

var fn = new Func<
    IEnumerable<object>,
    Func<PropertyInfo, bool>,
    Dictionary<Type, Type>,
    IEnumerable<object>>(
        ModelTransform.CopyList<object>);

var copyListMethod = fn.GetMethodInfo()
    .GetGenericMethodDefinition()
    .MakeGenericMethod(listTo);

copyListMethod.Invoke(null,
    new object[] { fromValue, whereProps, typeMap });

The first line uses a helper method we'll come back to, but all it's doing is getting the generic type of the IEnumerable list in this property, and assigning it to listTo. The next line is where we really begin performing this trick, where we lay out a Func with adequate parameters to match up with the specific CopyList() overload we intend to grab. Specifically, the CopyList() we want has 3 arguments, and returns IEnumerable<TTo>. Remember that Func takes its return type as its last generic arg, and that we're substituting object wherever there's a generic in the method we intend to grab. But, as you can see in this example, we do not need to substitute object anywhere else. For example, we know we want to pass a where clause that accepts a PropertyInfo and returns true/false (bool), and we just say those types right in the Func.

As the constructor arg to the Func, we pass CopyList() - but remember that the name CopyList is vague because of the method overloads. What's really cool is that C# is doing the hard work for you right now, by looking at the Func args, and identifying the right one. In fact, if you get the types or number of args wrong, Visual Studio will actually mark the line with an error:

No overload for 'CopyList' matches delegate 'Func...'

It's not smart enough to tell you what exactly you need to fix, but if you see that error you're close - you need to carefully double-check the args and return type and match them up exactly, replacing Generic args with object.

On the third line, we call the C# built-in .GetMethodInfo() and then .MakeGeneric(listTo). We have only one Generic to set for this, so we pass that in as listTo. If we had 2, we'd pass 2 args here. These Type args are replacing the object substitutions we made earlier.

And that's it - we can call copyListMethod(), with no strings, fully compile-safe. The final line makes the call, first passing null because it's a static method, then an object[] array with the 3 args. Done.

I said I'd come back to the ReflectionHelper method. Here it is:

public static Type GetIEnumerableType(Type type)
{
    var ienumerable = type.GetInterface(typeof(System.Collections.Generic.IEnumerable<>).FullName);
    var generics = ienumerable.GetGenericArguments();
    return generics[0];
}

Solution 3

This question is about 2 years old, but I came up with (what I think is) an elegant solution, and thought I'd share it with the fine folks here at StackOverflow. Hopefully it will help those who arrive here via various search queries.

The problem, as the poster stated, is to get the correct generic method. For example, a LINQ extension method may have tons of overloads, with type arguments nested inside other generic types, all used as parameters. I wanted to do something like this:

var where = typeof(Enumerable).GetMethod(
  "Where", 
  typeof(IQueryable<Refl.T1>), 
  typeof(Expression<Func<Refl.T1, bool>>
);

var group = typeof(Enumerable).GetMethod(
  "GroupBy", 
  typeof(IQueryable<Refl.T1>), 
  typeof(Expression<Func<Refl.T1, Refl.T2>>
);

As you can see, I've created some stub types "T1" and "T2", nested classes within a class "Refl" (a static class which contains all my various Reflection utility extension functions, etc. They serve as placeholders for where the type parameters would have normally went. The examples above correspond to getting the following LINQ methods, respectively:

Enumerable.Where(IQueryable<TSource> source, Func<TSource, bool> predicate);
Enumerable.GroupBy(IQueryable<Source> source, Func<TSource, TKey> selector);

So it should be clear that Refl.T1 goes where TSource would gone, in both of those calls; and the Refl.T2 represents the TKey parameter.The TX classes are declared as such:

static class Refl {
  public sealed class T1 { }
  public sealed class T2 { }
  public sealed class T3 { }
  // ... more, if you so desire.
}

With three TX classes, your code can identify methods containing up to three generic type parameters.

The next bit of magic is to implement the function that does the search via GetMethods():

public static MethodInfo GetMethod(this Type t, string name, params Type[] parameters)
{
    foreach (var method in t.GetMethods())
    {
        // easiest case: the name doesn't match!
        if (method.Name != name)
            continue;
        // set a flag here, which will eventually be false if the method isn't a match.
        var correct = true;
        if (method.IsGenericMethodDefinition)
        {
            // map the "private" Type objects which are the type parameters to
            // my public "Tx" classes...
            var d = new Dictionary<Type, Type>();
            var args = method.GetGenericArguments();
            if (args.Length >= 1)
                d[typeof(T1)] = args[0];
            if (args.Length >= 2)
                d[typeof(T2)] = args[1];
            if (args.Length >= 3)
                d[typeof (T3)] = args[2];
            if (args.Length > 3)
                throw new NotSupportedException("Too many type parameters.");

            var p = method.GetParameters();
            for (var i = 0; i < p.Length; i++)
            {
                // Find the Refl.TX classes and replace them with the 
                // actual type parameters.
                var pt = Substitute(parameters[i], d);
                // Then it's a simple equality check on two Type instances.
                if (pt != p[i].ParameterType)
                {
                    correct = false;
                    break;
                }
            }
            if (correct)
                return method;
        }
        else
        {
            var p = method.GetParameters();
            for (var i = 0; i < p.Length; i++)
            {
                var pt = parameters[i];
                if (pt != p[i].ParameterType)
                {
                    correct = false;
                    break;
                }
            }
            if (correct)
                return method;
        }
    }
    return null;
}

The code above does the bulk of the work -- it iterates through all the Methods in a particular type, and compares them to the given parameter types to search for. But wait! What about that "substitute" function? That's a nice little recursive function that will search through the entire parameter type tree -- after all, a parameter type can itself be a generic type, which may contain Refl.TX types, which have to be swapped for the "real" type parameters which are hidden from us.

private static Type Substitute(Type t, IDictionary<Type, Type> env )
{
    // We only really do something if the type 
    // passed in is a (constructed) generic type.
    if (t.IsGenericType)
    {
        var targs = t.GetGenericArguments();
        for(int i = 0; i < targs.Length; i++)
            targs[i] = Substitute(targs[i], env); // recursive call
        t = t.GetGenericTypeDefinition();
        t = t.MakeGenericType(targs);
    }
    // see if the type is in the environment and sub if it is.
    return env.ContainsKey(t) ? env[t] : t;
}

Solution 4

Another solution that you might find useful - it is possible to get a MethodInfo based on Expression.Call that already has a logic for overload resolution.

For example, in case you need to get some specific Enumerable.Where method that could be accomplished using the following code:

var mi = Expression.Call(typeof (Enumerable), "Where", new Type[] {typeof (int)},
            Expression.Default(typeof (IEnumerable<int>)), Expression.Default(typeof (Func<int, int, bool>))).Method;

Third argument in the example - describes types of generic arguments, and all other arguments - types of parameters.

In the same way it is possible to get even non static object generic methods.You need to change only first argument from typeof (YourClass) to Expression.Default(typeof (YourClass)).

Actually, I have used that approach in my plugin for .NET Reflection API. You may check how it works here

Solution 5

Let the compiler do it for you:

var fakeExp = (Expression<Func<IQueryable<int>, IQueryable<int>>>)(q => q.Where((x, idx) => x> 2));
var mi = ((MethodCallExpression)fakeExp.Body).Method.GetGenericMethodDefinition();

for the Where with index, or simply leave out the second parameter in the Where expression for the one without

Share:
18,035
Gamlor
Author by

Gamlor

Working on Spoon.net, mostly in the database and synchronization awesomeness. I’m mostly interested in the technologies behind the pretty user interface =).

Updated on June 06, 2022

Comments

  • Gamlor
    Gamlor almost 2 years

    I want to select the right generic method via reflection and then call it.

    Usually this is quite easy. For example

    var method = typeof(MyType).GetMethod("TheMethod");
    var typedMethod = method.MakeGenericMethod(theTypeToInstantiate);
    

    However the issue start when there are different generic overloads of the method. For example the static-methods in the System.Linq.Queryable-class. There are two definitions of the 'Where'-method

    static IQueryable<T> Where(this IQueryable<T> source, Expression<Func<T,bool>> predicate)
    static IQueryable<T> Where(this IQueryable<T> source, Expression<Func<T,int,bool>> predicate)
    

    This meand that GetMethod doesn't work, because it cannot destiguish the two. Therefore I want to select the right one.

    So far I often just took the first or second method, depending on my need. Like this:

    var method = typeof (Queryable).GetMethods().First(m => m.Name == "Where");
    var typedMethod = method.MakeGenericMethod(theTypeToInstantiate);
    

    However I'm not happy with this, because I make a huge assumption that the first method is the right one. I rather want to find the right method by the argument type. But I couldn't figure out how.

    I tried it with passing the 'types', but it didn't work.

            var method = typeof (Queryable).GetMethod(
                "Where", BindingFlags.Static,
                null,
                new Type[] {typeof (IQueryable<T>), typeof (Expression<Func<T, bool>>)},
                null);
    

    So has anyone an idea how I can find the 'right' generic method via reflection. For example the right version of the 'Where'-method on the Queryable-class?

  • JonDrnek
    JonDrnek about 11 years
    Great solution! I gave this answer a 500-point bonus in lieu of the deserved recognition.
  • Chris Moschini
    Chris Moschini about 11 years
    @RexM For what it's worth I'm not seeing how this is more effective at selecting a specific generic overload of a method than the approach I answered with, and it's definitely a lot more code. The answer I posted also doesn't require any runtime recursion.
  • JonDrnek
    JonDrnek about 11 years
    @ChrisMoschini for more complex situations than the original question, wherein we need to acquire the MethodInfo and only some of the generic parameters are known at that time, this provides an excellent usage story for the consuming developer. The volume of code is easy to incapsulate and cover with unit tests.
  • Chris Moschini
    Chris Moschini about 11 years
    @RexM Can you provide an example? Maybe that belongs as a separate question. Both approaches do fine with knowing only part of the generic arguments in acquiring the MethodInfo object - I'll update my answer to illustrate. I suspect that short of being unsure of the name of the method at compile-time, this method is less performant without providing additional benefit.
  • JonDrnek
    JonDrnek about 11 years
    @ChrisMoschini neither is the accepted answer, so I wouldn't worry too much about it. I found this answer helpful and effective and rewarded it accordingly.
  • Chris Moschini
    Chris Moschini about 11 years
    @atanamir Take another look at my answer - there are no Where or Selects in execution; the example I provided happens to be selecting a specific generic of the Where method, but I could have chosen any generic method. What's elegant about it is all the searching through methods and generics happen at compile time, rather than run time - that's the performance advantage.
  • atanamir
    atanamir about 11 years
    @ChrisMoschini OH, my sincere apologies! I was under the impression that yours was the accepted answer :(
  • atanamir
    atanamir about 11 years
    @ChrisMoschini That solution is very nice. Touche.
  • Chris Moschini
    Chris Moschini about 11 years
    @atanamir Thank you, an accidental discovery that came from reflecting in a lot of places I probably shouldn't.
  • Valerie
    Valerie over 10 years
    I don't know why this isn't the top answer! It is simple and type-safe. I got this to work for our code, with a slight change. I was calling a non-static method and had to first get the generic definition before making into a generic method: var methodInfo = method.Method.GetGenericMethodDefinition().MakeGenericMethod‌​(type1);
  • mheyman
    mheyman about 10 years
    I concur with Valerie. This is the way I now do this. No more mistyped strings giving me null reference exceptions... For the original question, a Func<> is needed instead of an Action<> and the GetGenericMethodDefinition() must be called.
  • Tsahi Asher
    Tsahi Asher almost 10 years
    I got a InvalidOperationException on the call to MakeGenericMethod trying to call the Queryable.Where: System.Linq.IQueryable1[System.Object] Where[Object](System.Linq.IQueryable1[System.Object], System.Linq.Expressions.Expression1[System.Func2[System.Obje‌​ct,System.Boolean]]) is not a GenericMethodDefinition. MakeGenericMethod may only be called on a method for which MethodBase.IsGenericMethodDefinition is true.
  • Tsahi Asher
    Tsahi Asher almost 10 years
    If you want to invoke the resulting MethodInfo, you would have to call .MakeGenericMethod() on it and invoke it's returned MethodInfo.
  • julealgon
    julealgon over 9 years
    I have always used this to get the specific generic methods at runtime, but I stumbled on a problem I could not solve. I'm implementing an EF interface that passes an instance of an internal class to me, and I need to get a generic method with runtime typing on this instance. It works fine inside of said interface method, but I wanted to extract the logic of getting the generic method info and wasn't able to, since it's an internal type and I couldn't instantiate it to access the method and put it into a Func. Is there a workaround to that, or do I need to pass the instance around?
  • Chris Moschini
    Chris Moschini over 9 years
    @julealgon That's complex enough that it merits its own question, since the answer could help others, and would likely benefit from code examples. Write up the Q and link to this answer!
  • Jeremy
    Jeremy about 9 years
    @julealgon I think I have your answer if it can cast to an interface you can use the interface method info. You will need to cast to Expression<Func<InterfaceType, object>> or Expression<Action<InterfaceType>>, convert to a MethodCallExpression, extract the method info, then get the generic type definition like this: interface MyInterface { TReturn MyGenericMethod<TSource, TReturn>(TSource input) } public static MethodInfo InterfaceMethod = (((Expression<Func<MyInterface, object>>)(x => x.MyGenericMethod(null as object))) as MethodCallExpression).Method.GetGenericMethodDefiniton();
  • MBoros
    MBoros about 9 years
    How does this work with overloaded methods (where there is no type overloding, only parameter overloading)?
  • Chris Moschini
    Chris Moschini about 9 years
    @MBoros Take a look at the Where example in my answer. Where has multiple parameter overloads that take the same generics. The approach I lay out here is designed to solve exactly the situation you describe. I'll add another example for methods that have no generics as time allows.
  • MBoros
    MBoros about 9 years
    @ChrisMoschini indeed, sorry. this solution seems very nice :)
  • Hopeless
    Hopeless over 8 years
    does this var where = typeof(Enumerable).GetMethod( "Where", typeof(IQueryable<Refl.T1>), typeof(Expression<Func<Refl.T1, bool>> ) even compile? As far as I know we have to pass in an array of Type, there is no params defined here. Also I don't think the use of dummy class here works. Once dummy classes are used, the obtained method should receive arguments of the exact dummy types which are unacceptable. Sorry for leaving a late comment but I think everything wrong in the answer should be edited.
  • Ghopper21
    Ghopper21 about 7 years
    This is nice, but I don't see where it's checking for the right number of parameters overall, regardless of whether they are generic. E.g. if the first method checked has a single parameter that matches the first desired parameters, it will be returned, even if it's the only parameter on the method and there were other parameters desired.
  • Riki
    Riki about 5 years
    How would you find any method if you only have the specific parameters? How would it look if you didn't hard-code the 3 types (queryable, expression, func)?
  • Adam Marshall
    Adam Marshall about 3 years
    While your answer may solve the question, including an explanation of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. You can edit your answer to add explanations and give an indication of what limitations and assumptions apply. - From Review
  • SvenL
    SvenL almost 2 years
    This simply not true (anymore?). See the answer below by Илья Любашов for a working example (although a little light on the explanation).
  • SvenL
    SvenL almost 2 years
    Not sure why this was voted down as this is (imho) the only answer that really an answers the question. The other answers maybe have more explanation... but dont answer the question about how to select the right one, besides searching your self with linq and such. Thanks @Илья Любашов, this helped a lot.