Esa respuesta anterior funciona para algunos casos, sin embargo:
- No maneja los tipos genéricos anidados, tales como un tipo de parámetro de
Action<IEnumerable<T>>
. Se tratarán todos los Action<>
como coincidencias, por ejemplo, string.Concat(IEnumerable<string>)
y string.Concat<T>(IEnumerable<T>)
coincidirán si se busca "Concat"
con el tipo IEnumerable<>
en el tipo de cadena. Lo que es realmente deseable es manejar los tipos genéricos anidados de manera recursiva, mientras se tratan todos los parámetros genéricos para que coincidan entre sí, independientemente del nombre, mientras que NO coinciden con los tipos concretos.
- Devuelve el primer método coincidente en lugar de arrojar una excepción si el resultado es ambiguo, como
type.GetMethod()
. Por lo tanto, puede obtener el método que quería si tiene suerte, o no.
- A veces será necesario especificar
BindingFlags
para evitar la ambigüedad, como cuando un método de clase derivado 'oculta' un método de clase base. Normalmente desea buscar métodos de clase base, pero no en un caso especializado donde sabe que el método que está buscando está en la clase derivada. O bien, es posible que sepa que está buscando un método estático vs instancia, público vs privado, etc. y no desea que coincida si no es exacto.
- No soluciona otro error importante con
type.GetMethods()
, ya que tampoco busca interfaces de base para métodos cuando busca un método en un tipo de interfaz. OK, tal vez sea exigente, pero es otro gran error en GetMethods()
que ha sido un problema para mí.
- Llamar a
type.GetMethods()
es ineficiente, type.GetMember(name, MemberTypes.Method, ...)
devolverá solo los métodos con un nombre coincidente en lugar de TODOS los métodos en el tipo.
- Como una nit-pick final, el nombre
GetGenericMethod()
podría ser engañoso, ya que podría estar tratando de encontrar un método no genérico que tenga un parámetro de tipo en algún tipo de parámetro debido a un tipo genérico de declaración.
Aquí hay una versión que trata todas estas cosas, y se puede utilizar como un reemplazo de uso general para el defectuoso GetMethod()
. Tenga en cuenta que se proporcionan dos métodos de extensión, uno con BindingFlags y otro sin (por conveniencia).
/// <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;
}
Tenga en cuenta que el método IsSimilarType(Type)
extensión puede hacerse pública y podría ser útil por sí misma. Lo sé, el nombre no es muy bueno, puedes inventar uno mejor, pero podría ser realmente largo explicar lo que hace. Además, agregue otra mejora comprobando para 'ref' y tipos de matriz (las referencias se ignoran para la coincidencia, pero las dimensiones de las matrices deben coincidir).
Así, así es como Microsoft debería hacerlo. Realmente no es tan difícil.
Sí, lo sé, puedes acortar algo de esa lógica usando Linq, pero no soy un gran admirador de Linq en rutinas de bajo nivel como este, y tampoco a menos que el Linq sea tan fácil de seguir como el código original, que a menudo NO es el caso, IMO.
Si te gusta LINQ, y se debe, se puede reemplazar la parte más interna de IsSimilarType()
con esto (8 vueltas en las líneas 1):
if (thisArguments.Length == arguments.Length)
return !thisArguments.Where((t, i) => !t.IsSimilarType(arguments[i])).Any();
Una última cosa: Si usted está buscando un método genérico con un parámetro genérico, como Method<T>(T, T[])
, deberá encontrar un tipo que sea un parámetro genérico (IsGenericParameter == true
) para pasar el tipo de parámetro (cualquiera lo hará, debido a la coincidencia de 'comodín'). Sin embargo, no puede simplemente hacer new Type()
- tiene que encontrar uno real (o compilar uno con TypeBuilder). Para hacerlo más fácil, agregué la declaración public class T
, y agregué lógica al IsSimilarType()
para verificarla y hacer coincidir cualquier parámetro genérico. Si necesita un T[]
, simplemente use T.MakeArrayType(1)
.
Obtener el tipo de Func me parece el obstáculo. –
Otro duplicado exacto: [select-right-generic-method-with-reflection] (http://stackoverflow.com/questions/3631547/select-right-generic-method-with-reflection) – nawfal