views:

184

answers:

4

How can I find a generic overloaded method? For example, Queryable's

public static IQueryable<TResult> Select<TSource , TResult> ( this IQueryable<TSource> source , Expression<Func<TSource , int , TResult>> selector );

I've looked for existing solutions, and they're either not generic enough (are based on the method's parameters count, etc.), need more parameters than I have (require generic type definitions or arguments), or just plain wrong (don't account for nested generics, etc.)

I have the defining class type — Type type, the method name — string name and the array of the parameter types (not the generic definitions) — Type[] types.

So far it seems I have to map each of prospective method's .GetGenericArguments() to a specific type by comparing the (generic type tree?) of the method's .GetParameters ().Select (p=>p.ParameterType) with the corresponding item in the types array, thus deducing the generic arguments of the method, so I can .MakeGenericMethod it.

This seems a bit too complicated for the task, so maybe I'm overthinking the whole thing.

Any help?

+1  A: 

Yes, you have to basically look through all the methods. There's no way of getting straight to a generic method just by specifying the number of type parameters etc.

However, one thing you can use is the fact that overloads with the same signature other than genericity are only overloaded by the number of generic type parameters. You can't use constraints or type parameter names to have:

 void Foo<T1>(String x)
 void Foo<T2>(String y)

etc.

Yes, this is all pretty complicated. I'd try pretty hard to avoid needing to do it if I were you.

Jon Skeet
+1  A: 

Calling generic methods with reflection genuinely can be this ugly. To resolve the correct MethodInfo I normally try to pick the simplest unique factor and work with that - which might mean the number of generic-parameters / method-parameters, etc. I try to avoid having to compare the unbound generic types themselves.

With dynamic in 4.0, I believe (although I'd need to check) that this can make many of these calls simpler (since it essentially does the method/generic resolution at runtime), but it doesn't work for extension methods ;-(

Another option is Expression, which can be a bit prettier in some ways, but you need to Compile() it etc, and Expression is itself complicated. (actually, forget that - it is still hard)

Marc Gravell
A: 

When you call GetMethod or InvokeMember on a Type instance, you are able to pass an custom subclass of the Binder class. A custom binder can alter the way members are selected, as they receive a list of candidate members to pick from.

Laurent Etiemble
A: 

Ok, so I just coded this, which is essentially a manual type inference, and I think should do what I need.

public static class TypeExtensions {

 public static Type GetTypeDefinition ( this Type type ) {
  return type.IsGenericType ? type.GetGenericTypeDefinition () : type;
 }

 public static IEnumerable<Type> GetImproperComposingTypes ( this Type type ) {
  yield return type.GetTypeDefinition ();
  if ( type.IsGenericType ) {
   foreach ( var argumentType in type.GetGenericArguments () ) {
    foreach ( var t in argumentType.GetImproperComposingTypes () ) yield return t;
   }
  }
 }

 private static Dictionary<Type , Type> GetInferenceMap ( ParameterInfo[] parameters , Type[] types ) {
  var genericArgumentsMap = new Dictionary<Type , Type> ();
  var match = parameters.All ( parameter => parameter.ParameterType.GetImproperComposingTypes ().Zip ( types[parameter.Position].GetImproperComposingTypes () ).All ( a => {
   if ( !a.Item1.IsGenericParameter ) return a.Item1 == a.Item2;
   if ( genericArgumentsMap.ContainsKey ( a.Item1 ) ) return genericArgumentsMap[a.Item1] == a.Item2;
   genericArgumentsMap[a.Item1] = a.Item2;
   return true;
  } ) );
  return match ? genericArgumentsMap : null;
 }

 public static MethodInfo MakeGenericMethod ( this Type type , string name , Type[] types ) {
  var methods = from method in type.GetMethods ()
       where method.Name == name
       let parameters = method.GetParameters ()
       where parameters.Length == types.Length
       let genericArgumentsMap = GetInferenceMap ( parameters , types )
       where genericArgumentsMap != null
       where method.GetGenericArguments ().Length == genericArgumentsMap.Keys.Count ()
       select new {
        method ,
        genericArgumentsMap
       };
  return methods.Select ( m => m.method.IsGenericMethodDefinition ? m.method.MakeGenericMethod ( m.method.GetGenericArguments ().Map ( m.genericArgumentsMap ).ToArray () ) : m.method ).SingleOrDefault ();
 }

}

So given

public class Foos {
 public void Foo<T1 , T2 , T3> ( int a , T1 b , IEnumerable<T2> c , Expression<Func<T1 , T3 , string>> d ) {
 }
 public void Foo<T1 , T2 , T3> ( int a , T1 b , IEnumerable<T2> c , Expression<Func<T1 , T3 , int>> d ) {
 }
 public void Foo () {
 }
 public void Foo ( string s ) {
 }
}

I can pick the method I want:

var method = typeof ( Foos ).MakeGenericMethod ( "Foo" , new[] { typeof ( int ) , typeof ( DateTime ) , typeof ( IEnumerable<string> ) , typeof ( Expression<Func<DateTime , double , int>> ) } );
method.Invoke ( new Foos () , new object[] { 1 , DateTime.Now , null , null } );

var method = typeof ( Foos ).MakeGenericMethod ( "Foo" , Type.EmptyTypes );
method.Invoke ( new Foos () , new object[] { } );

var method = typeof ( Foos ).MakeGenericMethod ( "Foo" , new[] { typeof ( string ) } );
method.Invoke ( new Foos () , new object[] { "zozo" } );

This seem to support non-generic methods, but doesn't support explicit generic arguments (obviously), and maybe needs some work, but that's the core of it.

chase