views:

598

answers:

5

I'm having some trouble using reflection to differentiate between a non-generic and a generic method on a generic class. Here's a test case I'm working with:

public class Foo<T>
{
  public string Bar( T value ) { return "Called Bar(T)"; }

  public string Bar( int value ) { return "Called Bar(int)"; }

  public static void CallBar<TR>(Foo<TR> foo)
  {
      var fooInfo = foo.GetType()
         .GetMethods()
         .Where(x => !x.IsGenericMethod && // doesn't filter out Bar(T)!
                 x.Name == "Bar" &&
                 x.GetParameters().First().ParameterType == typeof(int))
         // !Two identical MethodInfo results, how to choose between them?
         // Is there a gauranteed canonical ordering? Or is it undefined?
         .First();

      Console.WriteLine(fooInfo.Invoke(foo, new object[]{ 0 }));
  }
}

// prints Bar(T)...
Foo<int>.CallBar( new Foo<int>() );
+1  A: 

When using Foo<int>, the Bar(T) method is typed as Bar(int), making no distinction between it and the method with an int defined as the parameter.

To get the correct method definition of Bar(T), you can use typeof(Foo<>) instead of typeof(Foo<int>).

This will enable you to tell the difference between the two. Try the following code:


    public static void CallBar<TR>(Foo<TR> foo)
    {
        Func<MethodInfo, bool> match = m => m.Name == "Bar";

        Type fooType = typeof(Foo<>);
        Console.WriteLine("{0}:", fooType);
        MethodInfo[] methods = fooType.GetMethods().Where(match).ToArray();

        foreach (MethodInfo mi in methods)
        {
            Console.WriteLine(mi);
        }

        Console.WriteLine();

        fooType = foo.GetType();
        Console.WriteLine("{0}:", fooType);
        methods = fooType.GetMethods().Where(match).ToArray();

        foreach (MethodInfo mi in methods)
        {
            Console.WriteLine(mi);
        }
    }

This will output:

System.String Bar(T)
System.String Bar(Int32)

System.String Bar(Int32)
System.String Bar(Int32)

adrianbanks
+1  A: 

Try looking at the generic type definition: typeof(Foo<>). The methods will be in the same order.

  public class Foo<T> {
    public string Bar(T value) { return "Called Bar(T)"; }
    public string Bar(int value) { return "Called Bar(int)"; }
    public static void CallBar<TR>(Foo<TR> foo) {

      var footinfo = typeof(Foo<>).GetMethods();
      int i;
      for (i = 0; i < footinfo.Count(); ++i) {
        if (footinfo[i].Name == "Bar" && footinfo[i].GetParameters()[0].ParameterType.IsGenericParameter == false)
          break;
      }

      Console.WriteLine(foo.GetType().GetMethods()[i].Invoke(foo, new object[] { 0 }));
    }
  }
  // prints Bar(int)...
  Foo<int>.CallBar( new Foo<int>() );

The ContainsGenericParameters property is true for both Bar's in Foo<> and false for both Bar's in Foo, so its useless.

johnnycrash
+1  A: 

Unfortunately System.Reflection doesn't provide a good way to correlate a method on a constructed type with the corresponding method on the generic type definition from which it was constructed. There are two solutions I know of, neither one is perfect:

Solution #1: static TypeBuilder.GetMethod. There's a static version of GetMethod on TypeBuilder that accepts a generic constructed type and a MethodInfo for a method on a generic type definition, and returns the corresponding method on the specified generic type. In this example, calling TypeBuilder.GetMethod(Foo<int>, Foo<T>.Bar(T)) will give you Foo<int>.Bar(T-as-int) which you can then use to disambiguate between it and Foo<int>.Bar(int).

(The above example will not compile, naturally; I've used Foo<int> and Foo<T>.Bar(T) to mean the respective Type and MethodInfo objects which, which are easily obtainable but would make the example too complex).

The bad news is that this only works when the generic type definition is a TypeBuilder, i.e. when you're emitting a generic type.

Solution #2: MetadataToken. It's a little known fact that type members retain their MetadataToken in the transition from generic type definitions to generic constructed types. So in your example, Foo<T>.Bar(T) and Foo<int>.Bar(T-as-int) should share the same MetadataToken. That would allow you to do this:

var barWithGenericParameterInfo = typeof(Foo<>).GetMethods()
   .Where(mi => mi.Name == "Bar" && 
          mi.GetParameters()[0].ParameterType.IsGenericParameter);

var mappedBarInfo = foo.GetType().GetMethods()
    .Where(mi => mi.MetadataToken == genericBarInfo.MetadataToken);

(This will not compile either, unless I'm extremely lucky and managed to get it right the first time :) )

The problem with this solution is that MetadataToken wasn't meant for that (probably; the documentation is a little skimpy on that) and it feels like a dirty hack. Nevertheless, it works.

Avish
I think you're missing the point of the question here somewhat.
Paul Suart
How so? If I understand correctly, LBushkin wants to disambiguate between the two versions of Bar(int) after constructing `Foo<int>` from `Foo<T>`. I gave him two ways to tell them apart, by reliably identifying one of them. What do you think I've missed?
Avish
Avish - this is _exactly_ what I'm trying to do. This is very helpful - I'm not thrilled with using an undocumented side effect (feature?) of generic type information in my implementation. But it's a place to start. Thanks.
LBushkin
You're welcome, and I'm glad it's what you needed. I'd be honored if you'd mark this as an accepted answer.
Avish
A: 

As Eric Lippert points out, neither of them are generic methods; your class is generic, but you're passing a non-generic instance of the class. Therefore the methods aren't generic the way reflection sees it.

You should be on the right track if you change

foo.GetType()

to

foo.GetGenericTypeDefinition()

For more info, see MSDN's documentation.

Paul Suart