Tuesday, January 6, 2009

A way to manage extension methods in C#

I like C#'s extension methods, but I'm concerned about "polluting the namespace". I'm not the only one. I'm especially concerned about extensions to "object", as they appear everywhere. They can be useful, but wow that's a big impact. How to make valuable, wide-reaching extension methods possible without affecting lots of code that doesn't need the extension?

I've come up with an approach that helps me be picky about which extension methods are available in which contexts. That helps me manage how much pollution I am willing to accept. It is also means that most of the extension methods available in a given context are the ones that are relevant to the code I'm trying to write in that context.

Example:

namespace MyProject.Extensions.TypeExtensions
{
    static class _
    {
        public static bool DerivesFrom(this Type type, Type baseType)
        {
            while (type != null)
            {
                if (type.BaseType == baseType)
                {
                    return true;
                }

                type = type.BaseType;
            }

            return false;
        }
    }
}

Which is normally used like this:

    using Extensions.TypeExtensions;

    var typesDerivedFromFoo = from type in typeof(foo).Assembly.GetTypes()
                              where type.DerivesFrom(typeof(Foo))
                              select type;


Consequences

* That this approach works best if files are small, which means that my classes need to be small, too. If all your code is in a few files, there's no point to my method.

* All my extension methods are in the Extensions namespace.

* It's not friendly in languages that don't have extension methods. (You have to use the full name of the extension type):

Extensions.TypeExtensions._.DerivesFrom(type, baseType)

This isn't beautiful, but let's compare it to what you'd write if you didn't use extension methods at all:

TypeExtensions.DerivesFrom(type, baseType)

There is only an extra "_." and an extra "Extensions.", which isn't that much.

* My approach prevents you from using these methods as if they weren't extension methods, the way we did before:

using Extensions.TypeExtensions;
_.DerivesFrom(type, baseType);

Because "_." is ambiguous as soon as you bring in more than one extension.

* While C# requires that extension methods be defined in a class, the language doesn't care what the name of the class is. My approach reflects that, by giving the class a "nothing" name (and using the containing namespace as the visible name).

* The word "Extensions" appears twice in the namespace name, which is repetitive, redundant, says something that was already said. I'd rather call it "Extensions.Type", but that means that the name of type I'm extending ("Type") isn't available - I have to say "System.Type". Maybe that's a better choice.

1 comment:

Scott said...

I tried several times to the VB design team to add a "block scoped import" feature when I was implementing extension methods for the compiler, specifically so that you could bring extension into scope in only the place you would need them, but they refused to even consider it....

This is a good example of why it would be useful.