Using .Net extension methods as syntactic sugar without the toothache

Since extension methods were introduced to .NET in 2007, how to use them appropriately has always been a source of debate amongst its programmers.For those of you unfamiliar with them, the extension method feature allows you to add methods to an existing class without modifying the original type.  Extension methods are implemented as static methods that live inside non-nested, static classes. For example, I can extend the string class with a static method like GetWordCount():

namespace Utilities
{
  public static class MyExtensions
  {
      public static string GetWordCount(this string s)
     {
        return s.Split(' ').length;
     }

  }
}

The first parameter, using the this keyword, signifies which class is being extended. Then, so long as I've included the namespace of the static class in some other part of my codebase, I can call the newly created extension method as if it were implemented as a normal method in the class definition.

using Utilities;
...
if (s.GetWordCount()> 100)
{
  return "You're being too wordy!";
}

Extension methods are particularly handy for classes whose source code you don't have access to, like a third-party assembly or a class that's part of the framework. They help avoid more heavy-handed implementations like object composition or less elegant workarounds like a "helper" class, when all you want to do is spice up a class with a few extra methods helpful to your specific domain.

Too much sugar is bad for you

In reality, extension methods are just "syntactic sugar" on static methods. You could call the method above as MyExtensions.GetWordCount(s) with the same result. Erik Dietrich has a really nice explanation of this on his blog.

You'll also notice I "borrowed" his extension method example from there as well. Thanks Erik :).

Because they're quick to implement, it's easy to get into the habit of tacking on extension method after extension method to an existing class without giving it much additional thought. (I've done this personally before. Let's just say I have some pretty feature-rich string classes lying around). However, a bucket of extension methods on a class is really no better than the classic anti-pattern of a static "utility" or "helper" class. It's probably a sign that they belong in the original class, in some derived form of the original class, or there's another class (or three) waiting to be defined that could house these new methods in a more appropriate fashion.

That's why many of us have relegated extension methods as an absolute last resort or sworn them off altogether. They just seem to obfuscate the anti-pattern. And, the only thing worse than an anti-pattern is one that's hiding behind a mask.But, like most concepts in programming, the boundaries between good and evil are blurry. I've found that extension methods offer a clean way of augmenting a class's utility when in the context of another layer of the application. A touch of sugar without introducing an emerging toothache.

A touch of sugar is sometimes the best remedy

In our codebase, a Domain assembly houses all of the business objects within the system. These business objects are largely just POCOs exposed throughout the codebase---they're hydrated in repository layers, passed through service methods, and used on the application layer.

On the public API layer, I typically map the details of a business object into a public API response object.

On the web app layer, I usually extract some of its properties into the presentation model or just bundle the whole object within the presentation model.

When the latter occurs, there's often a scenario in which I need just a little bit more logic around the presentation of the data in that object. For example, on the issue detail page, we present the due date of an issue differently (via a specific CSS class) based on whether the issue is overdue, due soon, or due in the future. It also depends on whether the issue is already closed or fixed.

sdfasdf
The due date CSS class manifested on the issue detail page

This bit of logic only needs a couple of things: the due date and current status of the issue. Since both of these data points live neatly inside an IssueForDetail business object, this is where I choose to augment the object with an extension method that lives on the application layer (I've left the details of the implementation logic out for brevity).

namespace DoneDone.WebApp
{
  public static class IssueForDetailExtensions
  {
     public static string GetDueDateCSSClass(this IssueForDetail issue_for_detail)
     {
        var dueDateCSSClass = "";
        // Logic using the IssueForDetail object to assign the right CSS class...
        return dueDateCSSClass;
     }
  }
}

There are two main reasons I particularly prefer an extension method here to any other option.

First, in the context of the application layer, it makes for a very clean way to access the class on the view. I just call the method off of the business object instance like any normal instance method.

<span class="issue-due-date <%= Model.Issue.GetDueDateClass() %>">Due <%=Model.Issue.DueDate%></span>

Secondly, the business object only "knows" about the due date class method when it's used on the application layer. On all other layers, the extension method doesn't exist at all. I get all the syntactic benefit of the object at the layer where the extension method is applicable and no extra-bloated feeling on the other layers where it's not.

Without extension methods, I might park this logic in some generic utility class. But, that leans the codebase further toward the utility-class anti-pattern. Or, I might create a whole new type to house the domain object with the additional method trimmings. But, repeating that process a few times leans us toward a potentially excessive amount of classes seemingly not worth their weight. Or, I might shove the method inside the class definition itself so everyone has access to it while no one's looking and wait for the backlash later.

But, none of these options suit my tastes as well as using extension methods.

More from our blog...

We're on a quest to build the best issue tracking, help desk, and project management tool for all kinds of teams.

Subscribe to news and updates and follow us on Twitter.
We will never share your email address with third parties.

Give DoneDone a try today!

No credit card needed. Just sign up for a free trial, invite your team, and start getting things done with DoneDone.