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?
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");
}
}
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
}