2010-10-27 4 views
43

Я пытаюсь получить MethodInfo для Где метод перечислимых типов:GetMethod для универсального метода

typeof (Enumerable).GetMethod("Where", new Type[] { 
    typeof(IEnumerable<>), 
    typeof(Func<,>) 
}) 

но получить нуль. Что я делаю не так?

+1

Получение типа Func кажется препятствием для меня. –

+0

Другой точный дубликат: [select-right-generic-method-with-reflection] (http://stackoverflow.com/questions/3631547/select-right-generic-method-with-reflection) – nawfal

ответ

42

что предыдущий ответ работает для некоторых случаях, однако:

  • Он не обрабатывает вложенные обобщенные типы, такие как тип параметра 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, ...) будет возвращать только методы с совпадающим именем вместо ВСЕХ методов в типе.
  • В качестве окончательного варианта nit-pick имя 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).

+0

«Итак, так Microsoft должна была это сделать. Это действительно не так сложно». Да, кроме вашего кода все еще не работает. Если аргументом является System.Type, и значение, переданное в System.RuntimeType, не работает. –

+0

Кэмерон, я думаю, вы, вероятно, делаете что-то неправильно, чтобы иметь эту проблему. Может быть, вы вызвали GetType() для объекта Type? См. Ответ на этот пост: http://stackoverflow.com/questions/5737840/whats-the-difference-between-system-type-and-system-runtimetype-in-c –

+0

@Ken_Beckett Нет, я просто хочу позвонить метод с подписями 'SomeMethod (Тип t)', перейдя 'dynamicInstance.SomeMethod (other.GetType())'. Проблема заключается в том, что 'IsSimilarType' возвращает false для' System.RuntimeType' и 'System.Type'. Исправляя эту проблему, у меня возникли проблемы с вызовами методов с общими параметрами. Все ваши коды не работают во всех случаях, поэтому я бы сказал, что проблема ** является сложной. –

22

К сожалению, дженерики не очень хорошо поддерживаются в .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<,>) }); 
+0

Или в одной строке вернуть 'type.GetMethods(). FirstOrDefault (m => m.Name == name && m.GetParameters(). Выберите (p => p.ParameterType) .SequenceEqual (parameterTypes, новый SimpleTypeComparer()) ;; :) Добавление 'params' до последнего параметра будет приятнее. – nawfal

Смежные вопросы