GetMethod für generische Methode

Ich versuche, MethodInfo für Where-Methode des Typs Enumerable abzurufen:

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

aber werde null. Was mache ich falsch?

   

Diese vorherige Antwort funktioniert jedoch in einigen Fällen:

  • Es werden keine verschachtelten generischen Typen behandelt, z. B. ein Parametertyp der Action> . Es werden alle Action<> als Übereinstimmungen behandelt, zum Beispiel string.Concat(IEnumerable) und string.Concat(IEnumerable) werden beide übereinstimmen, wenn nach "Concat" mit dem Typ IEnumerable<> der Zeichenkettentyp Was wirklich wünschenswert ist, ist die rekursive Verarbeitung verschachtelter generischer Typen, während alle generischen Parameter unabhängig vom Namen als übereinstimmend behandelt werden, während sie NICHT mit konkreten Typen übereinstimmen.
  • Es gibt die erste übereinstimmende Methode zurück, anstatt eine Ausnahme type.GetMethod() wenn das Ergebnis mehrdeutig ist, wie type.GetMethod() . Also, Sie könnten die Methode bekommen, die Sie wollten, wenn Sie Glück haben oder nicht.
  • Manchmal müssen BindingFlags werden, um Mehrdeutigkeiten zu vermeiden, beispielsweise wenn eine abgeleitete classnmethode eine Basisklassenmethode “versteckt”. Normalerweise möchten Sie nach Basisklassenmethoden suchen, aber nicht in einem speziellen Fall, in dem Sie wissen, dass die gesuchte Methode in der abgeleiteten class enthalten ist. Oder Sie wissen vielleicht, dass Sie nach einer statischen vs-Instanzmethode suchen, öffentlich oder privat usw. und nicht übereinstimmen möchten, wenn sie nicht exakt ist.
  • Mit type.GetMethods() wird kein weiterer type.GetMethods() Fehler type.GetMethods() , da auch nach Basisschnittstellen für Methoden gesucht wird, wenn nach einer Methode für einen Schnittstellentyp gesucht wird. OK, vielleicht ist das wählerisch, aber es ist ein weiterer großer Fehler in GetMethods() , der ein Problem für mich war.
  • Der Aufruf von type.GetMethods() ist ineffizient, type.GetMember(name, MemberTypes.Method, ...) gibt nur Methoden mit einem übereinstimmenden Namen anstelle von ALLEN Methoden im Typ zurück.
  • Als abschließende GetGenericMethod() könnte der Name GetGenericMethod() irreführend sein, da Sie möglicherweise versuchen, eine nicht-generische Methode zu finden, die zufällig einen Typparameter irgendwo in einem Parametertyp aufgrund eines generischen Deklarationstyps hat.

Hier ist eine Version, die all diese Dinge anspricht und als allgemeiner Ersatz für die errorshafte GetMethod() . Beachten Sie, dass zwei Erweiterungsmethoden zur Verfügung stehen, eine mit BindingFlags und eine ohne (zur Vereinfachung).

 ///  /// Search for a method by name and parameter types. /// Unlike GetMethod(), does 'loose' matching on generic /// parameter types, and searches base interfaces. ///  ///  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); } ///  /// Search for a method by name, parameter types, and binding flags. /// Unlike GetMethod(), does 'loose' matching on generic /// parameter types, and searches base interfaces. ///  ///  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!"); } } } } ///  /// Special type used to match any generic parameter type in GetMethodExt(). ///  public class T { } ///  /// 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). ///  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; } 

Beachten Sie, dass die IsSimilarType(Type) -Erweiterungsmethode veröffentlicht werden kann und für sich allein nützlich sein kann. Ich weiß, der Name ist nicht großartig - Sie können gerne einen besseren Namen finden, aber es könnte sehr lange dauern, um zu erklären, was es tut. Außerdem habe ich noch eine weitere Verbesserung hinzugefügt, indem ich nach 'ref' und Array-Typen gesucht habe (Refs werden ignoriert, aber Array-Dimensionen müssen übereinstimmen).

So hätte Microsoft das machen sollen . Es ist wirklich nicht so schwer.

Ja, ich weiß, Sie können etwas von dieser Logik mit Linq verkürzen, aber ich bin kein großer Fan von Linq in Low-Level-Routinen wie dieser, und auch nicht, solange der Linq nicht so einfach zu befolgen ist wie der ursprüngliche Code. was oft nicht der Fall ist, IMO.

Wenn Sie Linq lieben und Sie müssen, können Sie den innersten Teil von IsSimilarType() durch diesen ersetzen (wird 8 Zeilen zu 1):

 if (thisArguments.Length == arguments.Length) return !thisArguments.Where((t, i) => !t.IsSimilarType(arguments[i])).Any(); 

Eine letzte Sache: Wenn Sie nach einer generischen Methode mit einem generischen Parameter suchen, z. B. Method(T, T[]) , müssen Sie einen Type finden, der ein generischer Parameter ist ( IsGenericParameter == true ) für den Parametertyp (irgendeinen wegen des 'Wildcard' Matching). Sie können jedoch nicht nur einen new Type() eingeben - Sie müssen einen echten new Type() finden (oder einen mit TypeBuilder erstellen). Um dies zu vereinfachen, habe ich die Deklaration der public class T hinzugefügt und IsSimilarType() Logik hinzugefügt, um nach dieser zu IsSimilarType() und einen beliebigen generischen Parameter zu finden. Wenn Sie ein T[] benötigen, verwenden Sie einfach T.MakeArrayType(1) .

Leider werden Generika in .NET Reflection nicht gut unterstützt. In diesem speziellen Fall müssen Sie GetMethods aufrufen und dann die Ergebnismenge für die gesuchte Methode filtern. Eine Extension-Methode wie die folgende sollte den Trick machen.

 public static class TypeExtensions { private class SimpleTypeComparer : IEqualityComparer { 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; } } 

Damit wird der folgende Code funktionieren:

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