10

I'd like to get a MethodInfo of a method from a generic class having a type parameter known only at runtime.

Here is how I would get a MethodInfo for a generic method from a non-generic class:

class MyClass
{
    public void MyMethod<T> (T arg)
    {
    }
}

static MethodInfo Resolve (Type type)
{
    Expression<Action<MyClass, object>> lambda = (c, a) => c.MyMethod (a);
    MethodCallExpression                call = lambda.Body as MethodCallExpression;

    return call
        .Method                        // Get MethodInfo for MyClass.MyMethod<object>
        .GetGenericMethodDefinition () // Get MethodInfo for MyClass.MyMethod<>
        .MakeGenericMethod (type);     // Get MethodInfo for MyClass.MyMethod<int>
}

Resolve (typeof (int)).Invoke (new MyClass (), new object[] {3});

Now if I want to try something similar with a generic class:

class MyClass<T>
{
    public void MyMethod (T arg)
    {
    }
}

static MethodInfo Resolve (Type type)
{
    Expression<Action<MyClass<object>, object>> lambda = (c, a) => c.MyMethod (a);
    MethodCallExpression                        call = lambda.Body as MethodCallExpression;

    return call
        .Method              // Get MethodInfo for MyClass<object>.MyMethod
        .SomeMagicMethod (); // FIXME: how can I get a MethodInfo 
                             // for MyClass<T>.MyMethod where typeof (T) == type?
}

Resolve (typeof (string)).Invoke (new MyClass<string> (), new object[] {"Hello, World!"});

Is it possible?

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
r3c
  • 498
  • 3
  • 8
  • that's tricky because `C.Meth` and `C.Meth` would be totally unrelated methods according to the .NET type system because their `DeclaringType`s are different. – usr Apr 11 '13 at 21:44
  • Can you fix your solution code? You are using the `method` variable as both a `MethodInfo` and a `MethodInfo[]`. And even search the array matching against the same variable. That makes no sense. – kjbartel Sep 03 '15 at 04:36
  • Variable `method` should indeed be an array, but the second search pass holds the solution so it's all but an error. Consider reading question again. – r3c Sep 04 '15 at 07:04

2 Answers2

2
public class MyClass<T>
{
    public void MyMethod(T arg, bool flag)
    {
        Console.WriteLine("type: MyClass<{0}>, arg: {1}, flag:{2}", typeof(T), 
            arg.ToString(), flag);
    }
    public void MyMethod(T arg)
    {
        Console.WriteLine("type: MyClass<{0}>, arg: {1}", typeof(T), arg.ToString());
    }
}
public class GenericInvokeTest
{
    static MethodInfo Resolve(Type type)
    {
        var name = ActionName<object>(x => (o) => x.MyMethod(o));
        var genericType = typeof(MyClass<>).MakeGenericType(new[] { type });
        MethodInfo genericTypeMyMethodInfo = genericType.GetMethod(name); // "MyMethod");
        genericTypeMyMethodInfo = genericType.GetMethod(name, new[] { type, typeof(bool) });
        return genericTypeMyMethodInfo;
    }
    public static void Test1()
    {
        Resolve(typeof(string))
            .Invoke(new MyClass<string>(), new object[] { "Hello, World!", true });
        // Resolve(typeof(string))
            .Invoke(new MyClass<string>(), new object[] { "Hello, World!" });
    }
}

To make it strong-typed you should simplify and use different approach:

1) Get the name of the action / method using expressions...

var name = ActionName<object>(x => (o) => x.MyMethod(o));

2) Then do the unavoidable reflection part

var genericType = typeof(MyClass<>).MakeGenericType(new[] { type });
MethodInfo genericTypeMyMethodInfo = genericType.GetMethod(name); // "MyMethod");


Where ActionName is taking similar approach as e.g. OnPropertyChanged(x => x.Property)
public static string ActionName<T>(Expression<Func<MyClass<T>, Action<T>>> expression)
{
    return GetMemberName(expression.Body);
}
public static string GetMemberName(Expression expression)
{
    switch (expression.NodeType)
    {
        case ExpressionType.Lambda:
            var lambdaExpression = (LambdaExpression)expression;
            return GetMemberName(lambdaExpression.Body);
        case ExpressionType.MemberAccess:
            var memberExpression = (MemberExpression)expression;
            var supername = GetMemberName(memberExpression.Expression);
            if (String.IsNullOrEmpty(supername))
                return memberExpression.Member.Name;
            return String.Concat(supername, '.', memberExpression.Member.Name);
        case ExpressionType.Call:
            var callExpression = (MethodCallExpression)expression;
            return callExpression.Method.Name;
        case ExpressionType.Convert:
            var unaryExpression = (UnaryExpression)expression;
            return GetMemberName(unaryExpression.Operand);
        case ExpressionType.Parameter:
            return String.Empty;
        default:
            throw new ArgumentException(
                "The expression is not a member access or method call expression");
    }
}
NSGaga-mostly-inactive
  • 14,052
  • 3
  • 41
  • 51
  • Thanks but the point was to use strongly-typed reflection only, not "GetMethod" or similar method which would not survive a refactoring. – r3c Apr 12 '13 at 08:08
  • I failed to see that point I guess :) I just updated the answer. – NSGaga-mostly-inactive Apr 12 '13 at 12:16
  • 1
    Good idea, but unfortunately it doesn't work for overloaded methods: it will throw an "AmbiguousMatchException" on the GetMethod call, and solving it would be quite difficult as it implies rewriting most of the (broken) "GetMethod" internal logic. – r3c Apr 12 '13 at 12:30
  • What's wrong with this `genericTypeMyMethodInfo = genericType.GetMethod(name, new[] { type, typeof(bool) });` – NSGaga-mostly-inactive Apr 12 '13 at 12:54
  • I updated - and it works for both. If something doesn't give me an example - this is abstract enough already. Anyway that wasn't part of your original question :) - this answers everything you pointed out. – NSGaga-mostly-inactive Apr 12 '13 at 12:57
  • There is still lots of possible ambiguities, here you have more details about it: http://stackoverflow.com/questions/11566613/how-do-i-distinguish-between-generic-and-non-generic-signatures-using-getmethod. Your solution would work if you use GetMethods() and search for an exact match instead of using name and parameters types only. I'll have a try and accept your answer if it works :) – r3c Apr 12 '13 at 15:32
  • of course ;). I'll take a look at the link later, busy atm. Cheers – NSGaga-mostly-inactive Apr 12 '13 at 16:18
  • I adapted your answer and used the `.MetadataToken` property to compare `MethodInfo`s and it works like a charm. Thanks :) – r3c Apr 13 '13 at 09:05
1

Working solution:

static MethodInfo Resolve (Type type)
{
    Expression<Action<MyClass<object>, object>> lambda = (c, a) => c.MyMethod (a);
    MethodCallExpression                        call = lambda.Body as MethodCallExpression;
    MethodInfo[]                                methods;
    Type                                        target;

    target = call
        .Method // Get MethodInfo for MyClass<object>.MyMethod
        .DeclaringType // Get typeof (MyClass<object>)
        .GetGenericTypeDefinition () // Get typeof (MyClass<>)
        .MakeGenericType (type); // Get typeof (MyClass<T>) where typeof (T) == type

    methods = target.GetMethods (BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); // We probably don't need static methods

    return Array.Find (methods, (m) => m.MetadataToken == method.MetadataToken); // Find MyClass<T>.MyMethod where typeof (T) == type
}
r3c
  • 498
  • 3
  • 8