我正在尝试检索Enumerable类型的Where方法的MethodInfo:
typeof (Enumerable).GetMethod("Where", new Type[] {
typeof(IEnumerable<>),
typeof(Func<,>)
})
但是我得到了 null。我做错了什么?
我正在尝试检索Enumerable类型的Where方法的MethodInfo:
typeof (Enumerable).GetMethod("Where", new Type[] {
typeof(IEnumerable<>),
typeof(Func<,>)
})
但是我得到了 null。我做错了什么?
Action<IEnumerable<T>>
。如果在字符串类型上搜索类型为 IEnumerable<>
的 "Concat"
,它将把所有的 Action<>
都视为匹配,例如 string.Concat(IEnumerable<string>)
和 string.Concat<T>(IEnumerable<T>)
都会匹配。真正需要的是递归处理嵌套的泛型类型,同时将所有泛型参数视为相互匹配而不考虑名称,同时不匹配具体类型。type.GetMethod()
一样。所以,你可能会得到你想要的方法,如果你很幸运的话,或者你可能不会得到。BindingFlags
以避免歧义,例如当派生类方法“隐藏”基类方法时。通常情况下,您希望找到基类方法,但在您知道要查找的方法在派生类中时,您不希望这样做。或者,您可能知道您正在查找静态方法 vs 实例方法、公共方法 vs 私有方法等,如果不精确匹配,就不想匹配。type.GetMethods()
的另一个主要缺陷,即在查找接口类型上的方法时,它也不会搜索基本接口。好吧,也许这有点挑剔,但对于我来说,这是 GetMethods()
的另一个重大缺陷。type.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'和数组类型(匹配时忽略引用,但数组维数必须匹配)添加了另一个改进。
因此,这就是Microsoft应该做的方式。真的并不难。
是的,我知道,您可以使用Linq缩短一些逻辑,但我不是Linq在低级例程中的铁杆粉丝,除非Linq与原始代码一样易于理解,但通常情况下并不是这种情况。
如果您喜欢Linq,并且您必须使用它,您可以将IsSimilarType()
的最内部部分替换为以下内容(将8行转换为1行):
if (thisArguments.Length == arguments.Length)
return !thisArguments.Where((t, i) => !t.IsSimilarType(arguments[i])).Any();
最后一件事:如果你正在寻找一个带有通用参数的通用方法,例如Method<T>(T, T[])
,那么你必须找到一个类型是通用参数(IsGenericParameter == true
)的Type来传递参数类型(任何一个都可以,因为有“通配符”匹配)。然而,你不能只是做new Type()
- 你必须找到一个真正的(TypeBuilder也行)。为了让这更容易,我添加了public class T
声明,并在IsSimilarType()
中添加了逻辑来检查它并匹配任何通用参数。如果你需要一个T[]
,只需使用T.MakeArrayType(1)
。
if (!parameterInfos[i].IsOptional && !parameterInfos[i].ParameterType.IsSimilarType(parameterTypes[i]))
需要进行其他代码更改以支持省略可选参数。 - Handcraftsman遗憾的是,泛型在.NET反射中得到的支持不够好。在这种情况下,您需要调用GetMethods方法,然后对结果集进行筛选以获取您要查找的方法。像以下这样的扩展方法应该可以解决问题。
public static class TypeExtensions
{
private class SimpleTypeComparer : IEqualityComparer<Type>
{
public bool Equals(Type x, Type y)
{
return x.Assembly == y.Assembly &&
x.Namespace == y.Namespace &&
x.Name == y.Name;
}
public int GetHashCode(Type obj)
{
throw new NotImplementedException();
}
}
public static MethodInfo GetGenericMethod(this Type type, string name, Type[] parameterTypes)
{
var methods = type.GetMethods();
foreach (var method in methods.Where(m => m.Name == name))
{
var methodParameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
if (methodParameterTypes.SequenceEqual(parameterTypes, new SimpleTypeComparer()))
{
return method;
}
}
return null;
}
}
有了这个,以下代码将起作用:
typeof(Enumerable).GetGenericMethod("Where", new Type[] { typeof(IEnumerable<>), typeof(Func<,>) });
type.GetMethods().FirstOrDefault(m => m.Name == name && m.GetParameters().Select(p => p.ParameterType).SequenceEqual(parameterTypes, new SimpleTypeComparer()));
:) 在最后一个参数上添加 params
会更好。 - nawfal