If the .Net BCL is drywall, Extension Methods are spackle

I know many people get upset at the very idea of Extension Methods, but I already consider them to be an indispensable part of my programming toolkit.  I’m seeing so many times where a quick extension method can make code be much more readable.  Other times an extension method against even a core BCL type seems to fill in a natural hole in the official API’s and knocks out a lot of duplication.

 

Example 1:  Custom Attributes

I do a lot of work with reflection in both StructureMap and my normal development.  Using custom attributes has always been an important extension point in .Net frameworks, but the code to access attributes is awkward – until Extension Methods.  I’ll bet you anything that at least a dozen other people reading this have written something exactly like this:

        public static T GetAttribute<T>(this ICustomAttributeProvider provider) where T : Attribute

        {

            var atts = provider.GetCustomAttributes(typeof(T), true);

            return atts.Length > 0 ? atts[0] as T : null;

        }

 

        public static void ForAttributesOf<T>(this ICustomAttributeProvider provider, Action<T> action) where T : Attribute

        {

            foreach (T attribute in provider.GetCustomAttributes(typeof(T), true))

            {

                action(attribute);

            }

        }

so that you can access attributes like:

            var formatAs = method.GetAttribute<FormatAsAttribute>();

and work with the attribute objects without all the icky casting and the goofy GetCustomAttributes business.

 

 

Example 2:  Writing Xml

Here’s another example for Xml manipulation from what became Fluent NHibernate:

        public static XmlElement AddElement(this XmlNode element, string name)

        {

            XmlElement child = element.OwnerDocument.CreateElement(name);

            element.AppendChild(child);

 

            return child;

        }

Programmatically building XmlDocument objects has always been tedious because of all the repetitious steps (create element, append element to its parent, set some attributes, rinse, repeat…) and the verbose API.  I found that a few extension methods shrank that code down into things like this:

            element.AddElement("key").AddElement("column").WithAtt("name", foreignKeyName).WithAtt("not-null", "true");

 

With the old Xml “push button API” this is about 6-7 lines of code.

 

 

 

Example 3:  Reading and Working from Xml

Here’s one last example.  When StructureMap loads an Xml configuration file, it will will allow you to “include” additional Xml files by specifying the files in an <Include> tag like this:

  <StructureMap Id="Master">

    <Include File="Include1.xml" />

    <Include File="Include2.xml" />

  </StructureMap>

In StructureMap 2.0 the code that acted on the <Instance> nodes looked like this:

 

Old .Net 2.0-ish Code

            string includedPath = null;

            try

            {

                XmlNodeList includeNodes = node.SelectNodes(XmlConstants.INCLUDE_NODE);

                foreach (XmlElement includeElement in includeNodes)

                {

                    XmlDocument includedDoc = new XmlDocument();

                    string fileName = includeElement.GetAttribute("File");

 

                    if (fileName == string.Empty)

                    {

                        throw new ApplicationException("The File attribute on the Include node is required");

                    }

 

                    try

                    {

                        includedPath = Path.Combine(folder, fileName);

                        includedDoc.Load(includedPath);

 

                        ConfigurationParser parser = new ConfigurationParser(includedDoc.DocumentElement);

                        list.Add(parser);

                    }

                    catch (Exception ex)

                    {

                        throw new StructureMapException(150, ex, fileName);

                    }

                }

            }

            catch (Exception ex)

            {

                throw new StructureMapException(100, includedPath, ex);

            }

Earlier this year I did a massive overhaul of the StructureMap core and used some .Net 3.5 language features to do this:

New .Net 3.5 Code

First, I wanted to separate the Xml manipulation code away from the code that locates and loads the additional Xml files.  I wrote an extension method against XmlNode (plus a little bit of a Fluent Interface) like this:

 

        public static XmlTextExpression ForTextInChild(this XmlNode node, string xpath)

        {

            return new XmlTextExpression(node, xpath);

        }

 

        public class XmlTextExpression

        {

            private readonly XmlNodeList _list;

 

            internal XmlTextExpression(XmlNode parent, string attributePath)

            {

                _list = parent.SelectNodes(attributePath);

            }

 

            public void Do(Action<string> action)

            {

                if (_list == null) return;

 

                foreach (XmlNode node in _list)

                {

                    action(node.InnerText);

                }

            }

        }

That’s a lot more code for Xml manipulation than the original sample, but the great thing is that I can reuse this code up above in other scenarios.

 

This extension method on XmlNode enabled me to write code that made working with the Xml data become almost declarative.  Instead of doing monotonous code to walk the Xml DOM and yank out the right nodes and attributes, I just say “do this thing (an Action<string> lambda) with each value of this attribute in every child node named ‘Include.’”  

            _structureMapNode.ForTextInChild("Include/@File").Do(fileName =>

            {

                string includedFile = Path.Combine(includePath, fileName);

                action(includedFile);

            });

and

            _structureMapNode.ForTextInChild("Assembly/@Name").Do(name => builder.AddAssembly(name));

and

                pluginElement.ForTextInChild("Setter/@Name").Do(prop => plugin.Setters.MarkSetterAsMandatory(prop));

 

 

 

But Wait!

But wait, extension methods are weird and my team won’t be able to understand it!  Get over it, this technique has been used successfully in other languages for years (Ruby, Objective C, even Javascript)

But wait, extension methods aren’t discoverable!  I’ll concede this one a little bit.  ReSharper has nice functionality to bring up extension methods with the CTRL-ALT-SPACE keyboard shortcut, and CTRL-B still works.  I think that this yet another example of how ReSharper (or the equivalent) usage radically changes the rules for programming.

About Jeremy Miller

Jeremy is the Chief Software Architect at Dovetail Software, the coolest ISV in Austin. Jeremy began his IT career writing "Shadow IT" applications to automate his engineering documentation, then wandered into software development because it looked like more fun. Jeremy is the author of the open source StructureMap tool for Dependency Injection with .Net, StoryTeller for supercharged acceptance testing in .Net, and one of the principal developers behind FubuMVC. Jeremy's thoughts on all things software can be found at The Shade Tree Developer at http://codebetter.com/jeremymiller.
This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://codebetter.com/blogs/jeremy.miller Jeremy D. Miller

    @Matt,

    I guess that’s a legitimate concern, but then again, I’ll answer that concern in only two words:

    “Continuous Integration”

  • Matt Poland

    To be honest, my biggest fear is having a developer write extension methods, use them pervasively, and then swap out the assembly that contained them for a new one that doesn’t contain them. That is even more of a worry if the assembly was provided by a third party (not your extension methods). I see the value but you better have a solid plan for how your projects will evolve, especially if you share libraries across projects.

    So for me, I see it like spackle that could potentially spontaneously disappear without notice.

  • http://blog.unhandled-exceptions.com Steve Bohlen

    >>Huh, I never even thought about doing this. I’m not sure you want to do that in every case.

    I’m curious: what’s the use-case where I *wouldn’t* want to do this? Is there a downside I’m overlooking?

    Reason I wonder is that we have traditionally (if there is such a thing for a language construct so relatively new) placed our extension methods into the same namepsace as the class being extended precisely to facilitate discoverability via intellisense (so that the extension method ‘appears’ as a natural part of the object’s interface with minimal extra hoops for the consuming dev to jump thru to get them).

    If there a downside to this approach that I’m overlooking and will bump into later and cause me pain, can you elaborate on what your think it might be?

  • http://dotnet.org.za/rudi Rudi Grobler

    Excellent article, tnx

  • Leyu

    I think comparing features as being “Old .Net 2.0-ish Code” and “New .Net 3.5 Code” confuses a lot of people.
    There is still much confusion regarding version number of .NET Framework, CLR and languages.

    It would be great comparing features from the language version perspective. C#2.0 vs C#3.0

  • http://codebetter.com/blogs/jeremy.miller Jeremy D. Miller

    “don’t most people put the extension method into the same namespace as the class being extended?”

    Huh, I never even thought about doing this. I’m not sure you want to do that in every case.

  • slapout

    I’ve heard of, but never used extension methods. What happens if, in a future version of the BCL, the object you extended ships with a method that has the same name as your extension method? Which method will get called?

  • http://blog.unhandled-exceptions.com Steve Bohlen

    >>You’ve got to have the namespace that contains the extension methods included before you’ll see the extension methods in Intellisense.

    This is true, but I start to wonder based on this offered ‘caveat’: don’t most people put the extension method into the same namespace as the class being extended? e.g., it doesn’t seem natural to me (or useful) to say my extension method is in System.Xml.Extensions if the thing it extends is in System.Xml.

    Are others doing this seperation of namespaces that would ‘defeat’ intellisense unless the consuming dev was aware enough to include the X.X.X.Extensions namespace or something similar?

  • http://www.fauberdemo.com/2008.html David Fauber

    “Keep in mind that many other platforms have very solid FOSS IDE’s.”

    Yeah, that’s kind of the basis for my point.

  • http://codebetter.com/blogs/jeremy.miller Jeremy D. Miller

    @Brian,

    You’ve got to have the namespace that contains the extension methods included before you’ll see the extension methods in Intellisense.

  • Brian

    What do you mean by ‘not discoverable’?

    >>But wait, extension methods aren’t discoverable! I’ll concede this one a little bit.

    I get it just fine in VS intellisense.

  • http://podwysocki.codebetter.com Matthew.Podwysocki

    @Tobin

    Unfortunately, we don’t know when that’s going to be for extension properties. I’m hoping for sooner rather than later, but not keeping any hopes up.

    In the mean time, F# supports extension everything for use only inside F#. (http://codebetter.com/blogs/matthew.podwysocki/archive/2008/09/12/object-oriented-f-more-extension-everything.aspx)

    Matt

  • Bryan Watts

    I really like employing Linq for custom attributes:

    public static IEnumerable GetAttributes(this ICustomAttributeProvider provider) where T : Attribute
    {
    …argnullcheck…

    return provider.GetCustomAttributes(typeof(T)).Cast();
    }

    public static T GetAttribute(this ICustomAttributeProvider provider) where T : Attribute
    {
    …argnullcheck…

    return provider.GetAttributes().FirstOrDefault();
    }

    public static void ForEachAttribute(this ICustomAttributeProvider provider, Action action) where T : Attribute
    {
    …argnullchecks…

    foreach(T attribute in provider.GetAttributes())
    {
    action(attribute);
    }
    }

  • http://codebetter.com/blogs/jeremy.miller Jeremy D. Miller

    @David,

    Keep in mind that many other platforms have very solid FOSS IDE’s.

  • http://www.fauberdemo.com/2008.html David Fauber

    “I think that this yet another example of how ReSharper (or the equivalent) usage radically changes the rules for programming.”

    I go back and forth on this. On the one hand, yes it makes me more productive in my current situation (employer pays for VS and Resharper).

    I worry about leaning too hard on the IDE when the IDE isn’t free though. Does this position .NET negatively to someone making a dev platform choice?

  • http://www.tobinharris.com/blog Tobin Harris

    I agree. They’re especially useful for writing shortcuts that you use over and over again.

    I just hope we can start writing extension *properties* in future versions of c#…

  • Ed McPadden

    Thanks! Great post! I am just starting to get into extension methods and I have done a lot of reflection code in the past. I will definitely be using this technique in the future.
    - Ed @emcpadden