Reflection for defining extension methods

C # has a method that uses reflection to determine if a method has been added to a class as an extension method?

For an extension method such as the one below, can you determine that Reverse () has been added to the string class?

public static class StringExtensions { public static string Reverse(this string value) { char[] cArray = value.ToCharArray(); Array.Reverse(cArray); return new string(cArray); } } 

We are looking for a mechanism to determine in unit testing that a developer has expanded the extension method. One of the reasons why this is done is that it is possible that a similar method will be added to the actual class by the developer, and if that were the case, the compiler would pick up this method.

+60
reflection c # extension-methods
Nov 18 '08 at 17:40
source share
6 answers

You should look in all assemblies where the extension method can be defined.

Look for classes decorated with ExtensionAttribute , and then methods in this class that are also decorated with ExtensionAttribute . Then check the type of the first parameter to see if it matches the type of interest.

Here is the complete code. It may be more strict (it does not check that the type is not nested, or that there is at least one parameter), but it should give you a helping hand.

 using System; using System.Runtime.CompilerServices; using System.Reflection; using System.Linq; using System.Collections.Generic; public static class FirstExtensions { public static void Foo(this string x) {} public static void Bar(string x) {} // Not an ext. method public static void Baz(this int x) {} // Not on string } public static class SecondExtensions { public static void Quux(this string x) {} } public class Test { static void Main() { Assembly thisAssembly = typeof(Test).Assembly; foreach (MethodInfo method in GetExtensionMethods(thisAssembly, typeof(string))) { Console.WriteLine(method); } } static IEnumerable<MethodInfo> GetExtensionMethods(Assembly assembly, Type extendedType) { var query = from type in assembly.GetTypes() 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 == extendedType select method; return query; } } 
+90
Nov 18 '08 at 17:42
source share

Based on John Skeet's answer, I created my own extension for the System.Type type.

 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>(); } } } 

Retrieves all assemblies from the current AppDomain and looks for advanced methods.

Using:

 Type t = typeof(Type); MethodInfo[] extendedMethods = t.GetExtensionMethods(); MethodInfo extendedMethodInfo = t.GetExtensionMethod("GetExtensionMethods"); 

The next step is to extend System.Type with methods that return all methods (also "regular" with extended)

+7
Jan 09 '12 at 19:07
source share

This will return a list of all extension methods defined in a particular type, including general ones:

 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; } 

There is only one problem with this: The return type is not the same as you expect with typeof (..), because it is a generic parameter type. To find all the extension methods for a given type, you will need to compare the GUIDs of all the base types and interfaces of the like type:

 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; } 

To be complete:

I save a dictionary of objects of type info, which I create when repeating all types of all assemblies:

 private Dictionary<Guid, TypeInfo> typeInfo = new Dictionary<Guid, TypeInfo>(); 

where TypeInfo is defined as:

 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; } } 
+4
Nov 04 '11 at 23:01
source share

To clarify the point that John was silent ... "Adding" an extension method to a class does not change the class in any way. This is just a little twist done by the C # compiler.

So, using your example, you can write

 string rev = myStr.Reverse(); 

but the MSIL written to the assembly will be exactly as if you wrote it:

 string rev = StringExtensions.Reverse(myStr); 

The compiler simply allows you to fool yourself into thinking that you are calling the String method.

+3
Nov 18 '08 at 18:05
source share

One of the reasons for trying this is that it is possible that a similar method will be added to the actual class by the developer, and if that were the case, the compiler would pick this method.

  • Suppose you define a void Foo extension method (this Customer someCustomer).
  • Assume also that the Client has been modified and the void Foo () method has been added.
  • Then the new method for the Client will cover / hide the extension method.

The only way to call the old Foo method at this point:

 CustomerExtension.Foo(myCustomer); 
+2
Nov 18 '08 at 19:05
source share
 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; } } 

Outputs:

False

True

0
Oct 20 '16 at 19:35
source share



All Articles