然而,前面的答案在某些情况下是有效的:
-
它不处理嵌套泛型类型,例如
Action<IEnumerable<T>>
. 它可以治疗所有人
Action<>
例如,作为匹配项,
string.Concat(IEnumerable<string>)
string.Concat<T>(IEnumerable<T>)
如果搜索
"Concat"
带类型
IEnumerable<>
在字符串类型上。真正需要的是递归地处理嵌套的泛型类型,同时将所有泛型参数视为彼此匹配,而不考虑名称,同时不匹配具体类型。
-
如果结果不明确(如
type.GetMethod()
-
有时需要具体说明
BindingFlags
为了避免歧义,例如当派生类方法“隐藏”基类方法时。您通常希望找到基类方法,但在您知道要查找的方法在派生类中的特殊情况下则不需要。或者,您可能知道您正在寻找一个静态vs实例方法、public vs private方法等,如果不完全匹配,则不希望匹配。
-
它没有解决
type.GetMethods()
,因为在接口类型上查找方法时,它也不会在基本接口中搜索方法。好吧,也许这太挑剔了,但这是
GetMethods()
-
打电话
类型.GetMethods()
效率低下,
type.GetMember(name, MemberTypes.Method, ...)
-
作为最后的挑剔,名字
GetGenericMethod()
可能会产生误导,因为您可能会试图找到一个非泛型方法,该方法碰巧由于泛型声明类型而在参数类型中的某个位置具有类型参数。
这是一个解决所有这些问题的版本,可以作为有缺陷的
GetMethod()
. 注意,提供了两个扩展方法,一个带有BindingFlags,另一个没有(为了方便)。
/// <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;
}
IsSimilarType(Type)
扩展方法可以公开,并且可以独立使用。我知道,这个名字不太好-欢迎你提出一个更好的名字,但可能要很长时间才能解释它的作用。此外,我还添加了另一个改进,检查“ref”和数组类型(匹配时忽略ref,但数组维度必须匹配)。
所以,微软就是这样
已经做到了。其实没那么难。
是的,我知道,你可以使用Linq来缩短一些逻辑,但是我不是Linq的超级粉丝,在像这样的低级例程中,我也不是Linq的超级粉丝,除非Linq和原来的代码一样容易理解,通常不是这样,IMO。
如果你爱林肯,而且你必须爱他,你可以替换他内心的大部分
IsSimilarType()
用这个(将8行变成1行):
if (thisArguments.Length == arguments.Length)
return !thisArguments.Where((t, i) => !t.IsSimilarType(arguments[i])).Any();
Method<T>(T, T[])
,则必须找到一个泛型参数类型(
IsGenericParameter == true
)传入参数类型(任何一个都可以,因为“通配符”匹配)。但是,你不能
new Type()
public class T
声明,并将逻辑添加到
IsSimilarType()
检查它并匹配任何泛型参数。如果你需要
T[]
,使用
T.MakeArrayType(1)