Я пытаюсь получить MethodInfo для Где метод перечислимых типов:GetMethod для универсального метода
typeof (Enumerable).GetMethod("Where", new Type[] {
typeof(IEnumerable<>),
typeof(Func<,>)
})
но получить нуль. Что я делаю не так?
Я пытаюсь получить MethodInfo для Где метод перечислимых типов:GetMethod для универсального метода
typeof (Enumerable).GetMethod("Where", new Type[] {
typeof(IEnumerable<>),
typeof(Func<,>)
})
но получить нуль. Что я делаю не так?
что предыдущий ответ работает для некоторых случаях, однако:
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 не будет так же легко следовать, как и исходный код, который часто не имеет места, ИМО.
Если вы любите 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)
.
«Итак, так Microsoft должна была это сделать. Это действительно не так сложно». Да, кроме вашего кода все еще не работает. Если аргументом является System.Type, и значение, переданное в System.RuntimeType, не работает. –
Кэмерон, я думаю, вы, вероятно, делаете что-то неправильно, чтобы иметь эту проблему. Может быть, вы вызвали GetType() для объекта Type? См. Ответ на этот пост: http://stackoverflow.com/questions/5737840/whats-the-difference-between-system-type-and-system-runtimetype-in-c –
@Ken_Beckett Нет, я просто хочу позвонить метод с подписями 'SomeMethod (Тип t)', перейдя 'dynamicInstance.SomeMethod (other.GetType())'. Проблема заключается в том, что 'IsSimilarType' возвращает false для' System.RuntimeType' и 'System.Type'. Исправляя эту проблему, у меня возникли проблемы с вызовами методов с общими параметрами. Все ваши коды не работают во всех случаях, поэтому я бы сказал, что проблема ** является сложной. –
К сожалению, дженерики не очень хорошо поддерживаются в .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<,>) });
Или в одной строке вернуть 'type.GetMethods(). FirstOrDefault (m => m.Name == name && m.GetParameters(). Выберите (p => p.ParameterType) .SequenceEqual (parameterTypes, новый SimpleTypeComparer()) ;; :) Добавление 'params' до последнего параметра будет приятнее. – nawfal
Получение типа Func кажется препятствием для меня. –
Другой точный дубликат: [select-right-generic-method-with-reflection] (http://stackoverflow.com/questions/3631547/select-right-generic-method-with-reflection) – nawfal