Я пытаюсь извлечь MethodInfo для метода Where для типа Enumerable:
typeof (Enumerable).GetMethod("Where", new Type[] {
typeof(IEnumerable<>),
typeof(Func<,>)
})
но получите значение null. Что я делаю неправильно?
Я пытаюсь извлечь MethodInfo для метода Where для типа Enumerable:
typeof (Enumerable).GetMethod("Where", new Type[] {
typeof(IEnumerable<>),
typeof(Func<,>)
})
но получите значение null. Что я делаю неправильно?
Тем не менее, предыдущий ответ работает в некоторых случаях:
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(), которая для меня была проблемой.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" и типы массивов (refs игнорируются для сопоставления, но размеры массивов должны совпадать).
Итак, как Microsoft должен сделать это. Это действительно не так сложно.
Да, я знаю, вы можете сократить некоторые из этой логики с помощью Linq, но я не являюсь большим поклонником Linq в низкоуровневых подпрограммах, подобных этому, и также, если Linq не будет так же легко следовать, как исходный код, который часто не имеет места, IMO.
Если вы любите 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), который должен пройти для тип параметра (любой будет делать из-за соответствия подстановочных знаков). Однако вы не можете просто сделать new Type() - вам нужно найти реальный (или создать один с TypeBuilder). Чтобы сделать это проще, я добавил объявление public class T и добавил логику в IsSimilarType(), чтобы проверить его и сопоставить любой общий параметр. Если вам нужен T[], просто используйте T.MakeArrayType(1).
К сожалению, дженерики не очень хорошо поддерживаются в .NET Reflection. В этом конкретном случае вам нужно вызвать 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<,>) });