Get a Generic Method Without Using Getmethods

GetMethod for generic method

That previous answer works for some cases, however:

  • It doesn't handle nested generic types, such as a parameter type of Action<IEnumerable<T>>. It will treat all Action<> as matches, for example, string.Concat(IEnumerable<string>) and string.Concat<T>(IEnumerable<T>) will both match if searching for "Concat" with type IEnumerable<> on the string type. What is really desirable is handling nested generic types recursively, while treating all generic parameters as matching each other regardless of name while NOT matching concrete types.
  • It returns the first method matched rather than throwing an exception if the result is ambiguous, like type.GetMethod() does. So, you might get the method you wanted if you're lucky, or you might not.
  • Sometimes it will be necessary to specify BindingFlags in order to avoid ambiguity, such as when a derived class method 'hides' a base class method. You normally want to find base class methods, but not in a specialized case where you know the method you're looking for is in the derived class. Or, you might know you're looking for a static vs instance method, public vs private, etc. and don't want to match if it's not exact.
  • It doesn't address another major fault with type.GetMethods(), in that it also doesn't search base interfaces for methods when looking for a method on an interface type. OK, maybe that's being picky, but it's another major flaw in GetMethods() that has been a problem for me.
  • Calling type.GetMethods() is inefficient, type.GetMember(name, MemberTypes.Method, ...) will return only methods with a matching name instead of ALL methods in the type.
  • As a final nit-pick, the name GetGenericMethod() could be misleading, since you might be trying to find a non-generic method that happens to have a type parameter somewhere in a parameter type due to a generic declaring type.

Here's a version that addresses all of those things, and can be used as a general-purpose replacement for the flawed GetMethod(). Note that two extension methods are provided, one with BindingFlags and one without (for convenience).

/// <summary>
/// Search for a method by name and parameter types.
/// Unlike GetMethod(), does 'loose' matching on generic
/// parameter types, and searches base interfaces.
/// </summary>
/// <exception cref="AmbiguousMatchException"/>
public static MethodInfo GetMethodExt( this Type thisType,
string name,
params Type[] parameterTypes)
{
return GetMethodExt(thisType,
name,
BindingFlags.Instance
| BindingFlags.Static
| BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.FlattenHierarchy,
parameterTypes);
}

/// <summary>
/// Search for a method by name, parameter types, and binding flags.
/// Unlike GetMethod(), does 'loose' matching on generic
/// parameter types, and searches base interfaces.
/// </summary>
/// <exception cref="AmbiguousMatchException"/>
public static MethodInfo GetMethodExt( this Type thisType,
string name,
BindingFlags bindingFlags,
params Type[] parameterTypes)
{
MethodInfo matchingMethod = null;

// Check all methods with the specified name, including in base classes
GetMethodExt(ref matchingMethod, thisType, name, bindingFlags, parameterTypes);

// If we're searching an interface, we have to manually search base interfaces
if (matchingMethod == null && thisType.IsInterface)
{
foreach (Type interfaceType in thisType.GetInterfaces())
GetMethodExt(ref matchingMethod,
interfaceType,
name,
bindingFlags,
parameterTypes);
}

return matchingMethod;
}

private static void GetMethodExt( ref MethodInfo matchingMethod,
Type type,
string name,
BindingFlags bindingFlags,
params Type[] parameterTypes)
{
// Check all methods with the specified name, including in base classes
foreach (MethodInfo methodInfo in type.GetMember(name,
MemberTypes.Method,
bindingFlags))
{
// Check that the parameter counts and types match,
// with 'loose' matching on generic parameters
ParameterInfo[] parameterInfos = methodInfo.GetParameters();
if (parameterInfos.Length == parameterTypes.Length)
{
int i = 0;
for (; i < parameterInfos.Length; ++i)
{
if (!parameterInfos[i].ParameterType
.IsSimilarType(parameterTypes[i]))
break;
}
if (i == parameterInfos.Length)
{
if (matchingMethod == null)
matchingMethod = methodInfo;
else
throw new AmbiguousMatchException(
"More than one matching method found!");
}
}
}
}

/// <summary>
/// Special type used to match any generic parameter type in GetMethodExt().
/// </summary>
public class T
{ }

/// <summary>
/// Determines if the two types are either identical, or are both generic
/// parameters or generic types with generic parameters in the same
/// locations (generic parameters match any other generic paramter,
/// but NOT concrete types).
/// </summary>
private static bool IsSimilarType(this Type thisType, Type type)
{
// Ignore any 'ref' types
if (thisType.IsByRef)
thisType = thisType.GetElementType();
if (type.IsByRef)
type = type.GetElementType();

// Handle array types
if (thisType.IsArray && type.IsArray)
return thisType.GetElementType().IsSimilarType(type.GetElementType());

// If the types are identical, or they're both generic parameters
// or the special 'T' type, treat as a match
if (thisType == type || ((thisType.IsGenericParameter || thisType == typeof(T))
&& (type.IsGenericParameter || type == typeof(T))))
return true;

// Handle any generic arguments
if (thisType.IsGenericType && type.IsGenericType)
{
Type[] thisArguments = thisType.GetGenericArguments();
Type[] arguments = type.GetGenericArguments();
if (thisArguments.Length == arguments.Length)
{
for (int i = 0; i < thisArguments.Length; ++i)
{
if (!thisArguments[i].IsSimilarType(arguments[i]))
return false;
}
return true;
}
}

return false;
}

Note that the IsSimilarType(Type) extension method can be made public and might be useful on its own. I know, the name isn't great - you're welcome to come up with a better one, but it might get really long to explain what it does. Also, I added yet another improvement by checking for 'ref' and array types (refs are ignored for matching, but arrays dimensions must match).

So, that's how Microsoft should have done it. It's really not that hard.

Yeah, I know, you can shorten some of that logic using Linq, but I'm not a huge fan of Linq in low-level routines like this, and also not unless the Linq is about as easy to follow as the original code, which is often NOT the case, IMO.

If you love Linq, and you must, you can replace the inner-most part of IsSimilarType() with this (turns 8 lines into 1):

if (thisArguments.Length == arguments.Length)
return !thisArguments.Where((t, i) => !t.IsSimilarType(arguments[i])).Any();

One last thing: If you're looking for a generic method with a generic parameter, such as Method<T>(T, T[]), you'll have to find a Type which is a generic parameter (IsGenericParameter == true) to pass in for the parameter type (any one will do, because of the 'wildcard' matching). However, you can't just do new Type() - you have to find a real one (or build one with TypeBuilder). To make this easier, I added the public class T declaration, and added logic to IsSimilarType() to check for it and match any generic parameter. If you need a T[], just use T.MakeArrayType(1).

Get a generic method without using GetMethods

Solved (by hacking LINQ)!

I saw your question while researching the same problem. After finding no good solution, I had the idea to look at the LINQ expression tree. Here's what I came up with:

public static MethodInfo GetOrderByMethod<TElement, TSortKey>()
{
Func<TElement, TSortKey> fakeKeySelector = element => default(TSortKey);

Expression<Func<IEnumerable<TElement>, IOrderedEnumerable<TElement>>> lamda
= list => list.OrderBy(fakeKeySelector);

return (lamda.Body as MethodCallExpression).Method;
}

static void Main(string[] args)
{
List<int> ints = new List<int>() { 9, 10, 3 };
MethodInfo mi = GetOrderByMethod<int, string>();
Func<int,string> keySelector = i => i.ToString();
IEnumerable<int> sortedList = mi.Invoke(null, new object[] { ints,
keySelector }
) as IEnumerable<int>;

foreach (int i in sortedList)
{
Console.WriteLine(i);
}
}

output: 10 3 9

EDIT: Here is how to get the method if you don't know the type at compile-time:

public static MethodInfo GetOrderByMethod(Type elementType, Type sortKeyType)
{
MethodInfo mi = typeof(Program).GetMethod("GetOrderByMethod", Type.EmptyTypes);

var getOrderByMethod = mi.MakeGenericMethod(new Type[] { elementType,
sortKeyType });
return getOrderByMethod.Invoke(null, new object[] { }) as MethodInfo;
}

Be sure to replace typeof(Program) with typeof(WhateverClassYouDeclareTheseMethodsIn).

How do you call GetMethod for a generic function that takes a generic parameter (without using GetMethods)?

It isn't readily available, because the types you need are actually generic type arguments that only exist in the method / parameter definition. For example, in your Test<T>(T val), the parameter type is "the T as defined by Test<T>. You can't construct that, because it isn't composed from anything. The only way to obtain that T is via GetParameters().

Basically, that leaves: the hard way - i.e. manually. For example:

var method1 = typeof(Program).GetMethods(flags).Single(x => x.Name == "Test"
&& x.IsGenericMethodDefinition && x.GetParameters().Length == 1
&& x.GetParameters()[0].ParameterType == x.GetGenericArguments()[0])
.MakeGenericMethod(paramTypes1);

Obviously it is simpler if you know there is only one Test(...) method:

var method = typeof(Program).GetMethod("Test", flags)
.MakeGenericMethod(paramTypes1);

How to resolve generic method ambiguity in C# reflection

If you don't want to use Type.GetMethods and iterate over the results you can use this overload of GetMethod and Type.MakeGenericMethodParameter which is available from .NET Core 2.1:

class MyClass
{
// Argument types
public class Bar<T> { }
public class Bar<T, U> { }

// Generic method overrides
private static void Foo<T>(Bar<T> b) { }
private static void Foo<T, U>(Bar<T, U> b) { }
}

typeof(MyClass)
.GetMethod(
"Foo",
1,
BindingFlags.NonPublic | BindingFlags.Static,
null,
new[] { Type.MakeGenericSignatureType(typeof(MyClass.Bar<>), Type.MakeGenericMethodParameter(0)) },
null
); // First Foo

typeof(MyClass)
.GetMethod(
"Foo",
2,
BindingFlags.NonPublic | BindingFlags.Static,
null,
new[] { Type.MakeGenericSignatureType(typeof(MyClass.Bar<,>), Type.MakeGenericMethodParameter(0),Type.MakeGenericMethodParameter(1)) },
null
); // Second Foo

UPD

Since Type.MakeGenericMethodParameter is not available for you the only option I see (if every method is not a part of generic class with the same generic type arguments) is to use GetMethods and filter the results. Simplest filter in this case would be number of generic parameters;

var secondMethod = typeof(MyClass).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
.Where(mi => mi.Name == "Foo" && mi.GetGenericArguments().Length == 2)
.First();

How to call a non-generic method of a class T from a method of a generic class?

The line

MethodInfo generic = method.MakeGenericMethod(typeof(T));

is not necessary at all.

At this point, when actually executing, T is not generic since it has been constructed (T is the actual type you want). method certainly isn't a generic method anyway.

You should be able to just do

typeof(T).GetMethod("DisplayProperty").Invoke(...

I also imagine you want to call Invoke with parameters (obj, null)

Type.GetMethod() for generic method

Instead of one of the Type.GetMethod() overloads, I ended up using GetMethods() on the target class and looping through all the members using an extension method to compare each parameter type (note C#7 only local function):

public static bool Similar(this Type reference, Type type)
{
if (reference.IsGenericParameter && type.IsGenericParameter)
{
return reference.GenericParameterPosition == type.GenericParameterPosition;
}

return ComparableType(reference) == ComparableType(type);

Type ComparableType(Type cType)
=> cType.IsGenericType ? cType.GetGenericTypeDefinition() : cType;
}

This considers two types to be "similar" if:

  • They are simple types and compare equal using the == operator
  • They are generic types and are of the type with the same index in the list of generic arguments (i.e. in SomeMethod<T,S>(S parameter), the one and only parameter type would be considered equal to that in SomeMethod<T1,T2>(T2 parm) but not SomeMethod<T,S>(T parameter)).
  • They are identical types with a nested generic parameter. In this case, the fact that it is a generic type is noted, but nothing about the parameter is probed further (so the parameter type on SomeMethod<T,S>(Action<T> parameter) would be "similar" to SomeMethod<T,S>(Action<S> parameter).

This is not ideal, but it turns out to be a surprisingly hard problem! It worked for my use case where it covered all my cases and because of the nature of the project (analysing legacy code) no new cases are likely to arise.

Similar() is used in the following extension on Type, which is intended to replace Type.GetMethod() and implements the loop I mentioned above:

public static MethodInfo GetMethodWithGenerics(
this Type type,
string name, Type[] parameters,
BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
{
var methods = type.GetMethods(flags);

foreach (var method in methods)
{
var parmeterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();

if (method.Name == name && parmeterTypes.Count() == parameters.Length)
{
bool match = true;

for (int i = 0; i < parameters.Length; i++)
match &= parmeterTypes[i].Similar(parameters[i]);

if (match)
return method;
}
}

return null;
}

As BurnsBA said below his answer, there seem to be some fundamental issues with the built-in reflection support for generics and there doesn't seem to be a simple solution to my original problem. I arrived at this answer after consideration of BurnBA's answer here and to the one he linked to on another question. This answer will be especially useful to anyone who wishes to produce a more thorough version of this comparison.

Anyone finding this useful should probably consider upvoting either or both of these.

How to get MethodInfo of a generic method on a non generic .NET type?

The problem is that the IEnumerable<> parameter you are passing to GetMethod is not specialized. It really is an IEnumerable<T>, where T is specified by the method you are trying to retrieve. But, we can't get T via MethodInfo.GetGenericArguments() since we don't have a reference to the method -- we are still trying to retrieve it.

Unfortunately, this is where the reflection API falls short. There is no Type.GetMethod() overload that allows you to distinguish between overloaded methods, where one is a generic method.

So with that said, you are stuck using Type.GetMethods() and filtering the results with a predicate of your choice. To get the method you are interested in, you can do the following.

void getMethod()
{
typeof(A).GetMethods().Where(m =>
m.IsGenericMethod &&
m.GetParameters()[0].ParameterType.GetGenericTypeDefinition()
== typeof(IEnumerable<>));
}

N.B. I haven't verified that the GetGenericTypeDefinition() call is required; you may be able to omit it. The idea is that you are transforming a type A<T> into A<>, but the runtime may already give it to you in that form.

Reflection: How to get a generic method?

var myMethod = myInstance.GetType()
.GetMethods()
.Where(m => m.Name == "MyMethod")
.Select(m => new {
Method = m,
Params = m.GetParameters(),
Args = m.GetGenericArguments()
})
.Where(x => x.Params.Length == 1
&& x.Args.Length == 1
&& x.Params[0].ParameterType == x.Args[0])
.Select(x => x.Method)
.First();

How do you call a generic method with out parameters by reflection?

I'm still interested to know what the syntax is for specifying an array of template types, or if it's not possible

I don't think it's possible to pass that kind of detailed type specification to GetMethod[s]. I think if you have a number of such Ms to look through, you have to get them all and then filter by the various properties of the MethodInfos and contained objects, eg as much of this as is necessary in your particular case:

var myMethodM =
// Get all the M methods
from mi in typeof(C).GetMethods()
where mi.Name == "M"

// that are generic with one type parameter
where mi.IsGenericMethod
where mi.GetGenericArguments().Length == 1
let methodTypeParameter = mi.GetGenericArguments()[0]

// that have two formal parameters
let ps = mi.GetParameters()
where ps.Length == 2

// the first of which is IEnumerable<the method type parameter>
where ps[0].ParameterType.IsGenericType
where ps[0].ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
where ps[0].ParameterType.GetGenericArguments()[0] == methodTypeParameter

// the second of which is ref <the method type parameter>
where ps[1].ParameterType.IsByRef
where ps[1].ParameterType.GetElementType() == methodTypeParameter

select mi;


Related Topics



Leave a reply



Submit