Gibt es in C # eine Technik, bei der mithilfe von Reflexion ermittelt wird, ob einer Klasse eine Methode als Erweiterungsmethode hinzugefügt wurde?
Kann bei einer Erweiterungsmethode wie der unten gezeigten festgestellt werden, dass Reverse () zur Zeichenfolgenklasse hinzugefügt wurde?
public static class StringExtensions
{
public static string Reverse(this string value)
{
char[] cArray = value.ToCharArray();
Array.Reverse(cArray);
return new string(cArray);
}
}
Wir suchen nach einem Mechanismus, mit dem beim Testen von Einheiten festgestellt werden kann, dass die Erweiterungsmethode vom Entwickler entsprechend hinzugefügt wurde. Ein Grund, dies zu versuchen, besteht darin, dass es möglich ist, dass der Entwickler der tatsächlichen Klasse eine ähnliche Methode hinzufügt, und wenn dies der Fall ist, wird der Compiler diese Methode aufgreifen.
quelle
[Extension]
Attribut auf eine Methode in einer statischen nicht generischen Klasse der obersten Ebene anwenden kann und trotzdem keine Erweiterungsmethode ist :) Nicht wahr?Basierend auf der Antwort von John Skeet habe ich meine eigene Erweiterung für den System.Type-Typ erstellt.
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; namespace System { public static class TypeExtension { /// <summary> /// This Methode extends the System.Type-type to get all extended methods. It searches hereby in all assemblies which are known by the current AppDomain. /// </summary> /// <remarks> /// Insired by Jon Skeet from his answer on http://stackoverflow.com/questions/299515/c-sharp-reflection-to-identify-extension-methods /// </remarks> /// <returns>returns MethodInfo[] with the extended Method</returns> public static MethodInfo[] GetExtensionMethods(this Type t) { List<Type> AssTypes = new List<Type>(); foreach (Assembly item in AppDomain.CurrentDomain.GetAssemblies()) { AssTypes.AddRange(item.GetTypes()); } var query = from type in AssTypes where type.IsSealed && !type.IsGenericType && !type.IsNested from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where method.IsDefined(typeof(ExtensionAttribute), false) where method.GetParameters()[0].ParameterType == t select method; return query.ToArray<MethodInfo>(); } /// <summary> /// Extends the System.Type-type to search for a given extended MethodeName. /// </summary> /// <param name="MethodeName">Name of the Methode</param> /// <returns>the found Methode or null</returns> public static MethodInfo GetExtensionMethod(this Type t, string MethodeName) { var mi = from methode in t.GetExtensionMethods() where methode.Name == MethodeName select methode; if (mi.Count<MethodInfo>() <= 0) return null; else return mi.First<MethodInfo>(); } } }
Es ruft alle Assemblys aus der aktuellen AppDomain ab und sucht nach erweiterten Methoden.
Verwendung:
Type t = typeof(Type); MethodInfo[] extendedMethods = t.GetExtensionMethods(); MethodInfo extendedMethodInfo = t.GetExtensionMethod("GetExtensionMethods");
Der nächste Schritt wäre, System.Type um Methoden zu erweitern, die alle Methoden zurückgeben (auch die "normalen" mit den erweiterten).
quelle
Dadurch wird eine Liste aller in einem bestimmten Typ definierten Erweiterungsmethoden zurückgegeben, einschließlich der generischen:
public static IEnumerable<KeyValuePair<Type, MethodInfo>> GetExtensionMethodsDefinedInType(this Type t) { if (!t.IsSealed || t.IsGenericType || t.IsNested) return Enumerable.Empty<KeyValuePair<Type, MethodInfo>>(); var methods = t.GetMethods(BindingFlags.Public | BindingFlags.Static) .Where(m => m.IsDefined(typeof(ExtensionAttribute), false)); List<KeyValuePair<Type, MethodInfo>> pairs = new List<KeyValuePair<Type, MethodInfo>>(); foreach (var m in methods) { var parameters = m.GetParameters(); if (parameters.Length > 0) { if (parameters[0].ParameterType.IsGenericParameter) { if (m.ContainsGenericParameters) { var genericParameters = m.GetGenericArguments(); Type genericParam = genericParameters[parameters[0].ParameterType.GenericParameterPosition]; foreach (var constraint in genericParam.GetGenericParameterConstraints()) pairs.Add(new KeyValuePair<Type, MethodInfo>(parameters[0].ParameterType, m)); } } else pairs.Add(new KeyValuePair<Type, MethodInfo>(parameters[0].ParameterType, m)); } } return pairs; }
Hier gibt es nur ein Problem: Der zurückgegebene Typ ist nicht derselbe, den Sie mit typeof (..) erwarten würden, da es sich um einen generischen Parametertyp handelt. Um alle Erweiterungsmethoden für einen bestimmten Typ zu finden, müssen Sie die GUID aller Basistypen und Schnittstellen des Typs wie folgt vergleichen:
public List<MethodInfo> GetExtensionMethodsOf(Type t) { List<MethodInfo> methods = new List<MethodInfo>(); Type cur = t; while (cur != null) { TypeInfo tInfo; if (typeInfo.TryGetValue(cur.GUID, out tInfo)) methods.AddRange(tInfo.ExtensionMethods); foreach (var iface in cur.GetInterfaces()) { if (typeInfo.TryGetValue(iface.GUID, out tInfo)) methods.AddRange(tInfo.ExtensionMethods); } cur = cur.BaseType; } return methods; }
Vollständig sein:
Ich behalte ein Wörterbuch mit Typinfoobjekten, das ich beim Iterieren aller Typen aller Assemblys erstelle:
private Dictionary<Guid, TypeInfo> typeInfo = new Dictionary<Guid, TypeInfo>();
wo das
TypeInfo
definiert ist als:public class TypeInfo { public TypeInfo() { ExtensionMethods = new List<MethodInfo>(); } public List<ConstructorInfo> Constructors { get; set; } public List<FieldInfo> Fields { get; set; } public List<PropertyInfo> Properties { get; set; } public List<MethodInfo> Methods { get; set; } public List<MethodInfo> ExtensionMethods { get; set; } }
quelle
(constraint, m}
ist nicht richtig, da die Einschränkungen "und" nicht "oder" sind.Um einen Punkt zu verdeutlichen, den Jon beschönigt hat ... Das Hinzufügen einer Erweiterungsmethode zu einer Klasse ändert die Klasse in keiner Weise. Es ist nur ein bisschen Spinning, das vom C # -Compiler ausgeführt wird.
Anhand Ihres Beispiels können Sie also schreiben
string rev = myStr.Reverse();
Die in die Assembly geschriebene MSIL ist jedoch genau so, als hätten Sie sie geschrieben:
string rev = StringExtensions.Reverse(myStr);
Der Compiler lässt Sie sich nur täuschen, dass Sie eine String-Methode aufrufen.
quelle
Die einzige Möglichkeit, die alte Foo-Methode zu diesem Zeitpunkt aufzurufen, ist:
quelle
void Main() { var test = new Test(); var testWithMethod = new TestWithExtensionMethod(); Tools.IsExtensionMethodCall(() => test.Method()).Dump(); Tools.IsExtensionMethodCall(() => testWithMethod.Method()).Dump(); } public class Test { public void Method() { } } public class TestWithExtensionMethod { } public static class Extensions { public static void Method(this TestWithExtensionMethod test) { } } public static class Tools { public static MethodInfo GetCalledMethodInfo(Expression<Action> expr) { var methodCall = expr.Body as MethodCallExpression; return methodCall.Method; } public static bool IsExtensionMethodCall(Expression<Action> expr) { var methodInfo = GetCalledMethodInfo(expr); return methodInfo.IsStatic; } }
Ausgänge:
Falsch
Wahr
quelle