2013-04-11 11 views
10

Mi piacerebbe ottenere un MethodInfo di un metodo da una classe generica avente un parametro di tipo noto solo al runtime.Come trovare MethodInfo per un metodo di una classe generica utilizzando il reflection fortemente tipizzato?

Ecco come vorrei avere un MethodInfo per un metodo generico da una classe non generica:

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

Ora, se voglio provare qualcosa di simile con una classe generica:

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

È possibile?

+0

che è difficile perché 'C .Meth' e' C .Meth' sarebbe metodi totalmente estranei a seconda del sistema di tipo .NET perché il loro 'DeclaringType' s sono diversi. – usr

+0

È possibile correggere il codice della soluzione? Stai usando la variabile 'method' sia come' MethodInfo' che come 'MethodInfo []'. E anche cercare la corrispondenza dell'array con la stessa variabile. Non ha senso. – kjbartel

+0

Il 'metodo' variabile' dovrebbe essere effettivamente un array, ma il secondo passaggio di ricerca contiene la soluzione quindi è tutto tranne un errore. Considera di leggere di nuovo la domanda. – r3c

risposta

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

per renderla forte-digitato si dovrebbe semplificare e utilizzare approccio diverso:

1) Prendi il name dell'azione/metodo che utilizza espressioni ...

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

2) Quindi fare la parte di riflessione inevitabile

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


Dove ActionName sta adottando un approccio simile come ad es. 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"); 
    } 
} 
+0

Grazie, ma il punto era usare solo riflessioni fortemente tipizzate, non "GetMethod" o un metodo simile che non sarebbe sopravvissuto a un refactoring. – r3c

+0

Non sono riuscito a vedere quel punto immagino :) Ho appena aggiornato la risposta. – NSGaga

+1

Buona idea, ma sfortunatamente non funziona con metodi sovraccaricati: genererà una "AmbiguousMatchException" sulla chiamata GetMethod, e risolverlo sarebbe piuttosto difficile in quanto implica la riscrittura della maggior parte della logica interna (interrotta) "GetMethod". – r3c

1

Soluzione di lavoro:

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 
} 
Problemi correlati