Extension Methods on Types You Own?


It’s no secret that I’m a fan of using extension methods to make

code more concise and expressive. This is particularly handy for

enhancing APIs outside of your control, from the base class library to

ASP.NET MVC and SharePoint. However, there are certain situations where

it might be useful to use extension methods even though you have the

option to add those methods to the class or interface itself. Consider

this simplified caching interface:

public interface ICacheProvider
{
T Get<T>(string key);
void Insert<T>(string key, T value);
}

And a simple application of the decorator pattern to implement a cached repository:

public class CachedAwesomeRepository : IAwesomeRepository
{
private readonly IAwesomeRepository awesomeRepository;
private readonly ICacheProvider cacheProvider;

public CachedAwesomeRepository(IAwesomeRepository awesomeRepository, ICacheProvider cacheProvider)
{
this.awesomeRepository = awesomeRepository;
this.cacheProvider = cacheProvider;
}

public Awesome GetAwesome(string id)
{
var awesome = cacheProvider.Get<Awesome>(id);
if(awesome == null)
cacheProvider.Insert(id, (awesome = awesomeRepository.GetAwesome(id)));
return awesome;
}
}

So far, so good. However, as caching is used more often it becomes

clear that there’s a common pattern that we might want to extract:

    T ICacheProvider.GetOrInsert<T>(string key, Func<T> valueFactory)
{
T value = Get<T>(key);
if(value == default(T))
Insert(key, (value = valueFactory()));
return value;
}

Which would reduce GetAwesome() to a single, simple expression:

public Awesome GetAwesome(string id)
{
return cacheProvider.GetOrInsert(id, () => awesomeRepository.GetAwesome(id));
}

Now I just need to decide where GetOrInsert() lives. Since I control ICacheProvider,

I could just add another method to the interface and update all its

implementers. However, after starting down this path, I concluded this

was not desirable for a number of reasons:

  1. The implementation of GetOrInsert within each cache provider was essentially identical.
  2. Tests using a mocked ICacheProvider now needed to know if the code
under test used GetOrInsert() or Get() + Insert(), coupling the test
  
too tightly to the implementation. Furthermore, natural tests along the
  
lines of &#8220;Should return cached value&#8221; and &#8220;Should insert value from
  
repository if not cached&#8221; were replaced with a single
  
implementation-specific test: &#8220;Should return value from GetOrInsert&#8221;.   3. Most importantly, I came to realize that GetOrInsert() really just
  
isn&#8217;t something that a cache does, so why should it be part of the
  
interface?

So instead I have a handy GetOrInsert() extension

method (conversion is left as an exercise for the reader) that I can

use to clean up my caching code without needing to change any of my

cache providers or tests for existing consumers.

The question is really analogous to whether or not Select() and Where() should be part of IEnumerable<T>. They are certainly useful ways to consume the interface, just as GetOrInsert() is, but they exist outside of what an IEnumerable<T> really is.

Quick Tip: Parse String to Nullable Value