views:

3151

answers:

4

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.

+20  A: 

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;
    }
}
Jon Skeet
David B
True, that's a reasonably simple test. It's a shame that there isn't an equivalent of IsDefined for Type :) I'll edit the answer with that code.
Jon Skeet
Doesn't work if the extended type is a generic.Extension-methods for IQueryable will match, but not for IQueryable<>, for example.I think it fails on the ParameterType.
Seb Nilsson
@Seb: Yes, it would take a fair amount more effort to make it work for generic methods. Feasible, but tricky.
Jon Skeet
+1 Great query! Loads of information in just that one snippet...
Andrew Siemer
+2  A: 

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.

James Curran
Yes. I'm completely aware that the compiler is working some "magic" to hide the details. That's one of the reasons we're interested in detecting in a unit test whether the method is an extension method or not.
Mike Chess
A: 

Jon's original answer was enough to get me going in a direction that worked. The revised answer with the code sample with a Linq example was a nice bonus. Thanks!

Mike Chess
LINQ is often handy for reflection like this. Gotta love LINQ to Objects :)
Jon Skeet
+1  A: 

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);
David B