Reflection to Identify Extension Methods

C#ReflectionExtension Methods

C# Problem Overview


In C# is there a technique using reflection to determine if a method has been added to a class as an extension method?

Given an extension method such as the one shown below is it possible to 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're looking for a mechanism to determine in unit testing that the extension method was appropriately added by the developer. One reason to attempt this is that it is possible that a similar method would be added to the actual class by the developer and, if it was, the compiler will pick that method up.

C# Solutions


Solution 1 - C#

You have to look in all the assemblies where the extension method may be defined.

Look for classes decorated with ExtensionAttribute, and then methods within that class which are also decorated with ExtensionAttribute. Then check the type of the first parameter to see if it matches the type you're interested in.

Here's some complete code. It could be more rigorous (it's not checking that the type isn't 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;
    }
}

Solution 2 - C#

Based on John Skeet's answer I've created my own extension to 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>();
		}
	}
}

It get's all assemblies from the current AppDomain and searches for extended methods.

Usage:

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

The next step would be to extend System.Type with methods, which returns all Methods (also the "normal" ones with the extended ones)

Solution 3 - C#

This will return a list of all extension methods defined in a certain type, including the generic 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's only one problem with this: The Type returned is not the same you'd expect with typeof(..), because it's a generic parameter type. In order to find all the extension methods for a given type you'll have to compare the GUID of all the base types and interfaces of the Type like:

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 keep a dictionary of type info objects, that I build when iterating all the types of all assemblies:

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

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

Solution 4 - C#

To clarify a point Jon glossed over... "Adding" an extension method to a class does not change the class in any way. It's just a little bit of spinning performed by the C# compiler.

So, using your example, you may write

string rev = myStr.Reverse();

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

string rev = StringExtensions.Reverse(myStr);

The compiler is merely letting you fool yourself into thinking you are calling an method of String.

Solution 5 - C#

> One reason to attempt this is that it is possible that a similar method would be added to the actual class by the developer and, if it was, the compiler will pick that method up.

  • Suppose an extension method void Foo(this Customer someCustomer) is defined.
  • Suppose, also, that Customer is modified and the method void Foo() is added.
  • Then, the new method on Customer will cover/hide the extension method.

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

CustomerExtension.Foo(myCustomer);

Solution 6 - C#

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMike ChessView Question on Stackoverflow
Solution 1 - C#Jon SkeetView Answer on Stackoverflow
Solution 2 - C#Stelzi79View Answer on Stackoverflow
Solution 3 - C#DrakarahView Answer on Stackoverflow
Solution 4 - C#James CurranView Answer on Stackoverflow
Solution 5 - C#Amy BView Answer on Stackoverflow
Solution 6 - C#billyView Answer on Stackoverflow