Reputation: 131676
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>() );
Upvotes: 3
Views: 2217
Reputation: 6713
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.
Upvotes: 0
Reputation: 4626
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.
Upvotes: 3
Reputation: 5344
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.
Upvotes: 1
Reputation: 82944
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)
Upvotes: 1
Reputation: 30293
I think ContainsGenericParameters is what you're looking for, according to the documentation:
http://msdn.microsoft.com/en-us/library/system.reflection.methodinfo.isgenericmethod.aspx http://msdn.microsoft.com/en-us/library/system.reflection.methodinfo.containsgenericparameters.aspx
Upvotes: 0