Welcome to WindowsClient.net | Sign in | Join
Tuesday, October 28, 2008 5:02 PM rendle

Functional Shortcuts 1 - Method caching

We all have coding patterns that we like, and use a lot. Some of them can be quite laborious, though, so if you're anything like me you'll be looking for shortcuts. C++ programmers have a powerful macro language that can provide a shorthand for a complicated block of code, which is then expanded during the compile process. C# doesn't have a macro language, but using anonymous delegates and closures, it is possible to achieve something similar, maybe even better. I've come up with a few little tricks in this area, so I'm going to share them here over the next few updates.

This example looks at an optimisation technique wherein the return values from slow-running deterministic methods (i.e. methods which, given a certain parameter, will always return the same value) are cached locally, and returned from the cache on subsequent calls. The pattern looks like this:

public class Example
{
  private Dictionary<Uri, string> cache = new Dictionary<Uri, string>();
  private object lockObject = new object();

  public string GetFromWeb(Uri uri)
  {
    if (!cache.ContainsKey(uri)) // Check cache
    {
      lock (lockObject) // Thread safety!
      {
        // Check cache again in case key was added while waiting for lock
        if (!cache.ContainsKey(uri))
        {
          // Process request
          var webClient = new WebClient();
          var reader = new StreamReader(webClient.OpenRead(uri));

          // Add result to cache
          cache.Add(uri, reader.ReadToEnd());
        }
      }
    }

    // Return cached value
    return cache[uri];
  }
}

In this - admittedly simple - example, that's a poor ratio of boilerplate to actual code. On top of that, we've had to declare a couple of variables in the containing class which exist only to facilitate this optimisation pattern in this method. After typing out this pattern dozens of times (well, at least ten. OK, five. Fine, it was twice, I get bored quickly) I wondered if there was a better way. And there is.

First off, let's refactor the code above to extract the three lines that are actually doing the work and put them in their own method:

public class Example
{
  private Dictionary<Uri, string> cache = new Dictionary<Uri, string>();
  private object lockObject = new object();

  public string GetFromWeb(Uri uri)
  {
    if (!cache.ContainsKey(uri)) // Check cache
    {
      lock (lockObject) // Thread safety!
      {
        // Check cache again in case key was added while waiting for lock
        if (!cache.ContainsKey(uri))
        {
          // Add result to cache
          cache.Add(uri, ProcessRequest(uri));
        }
      }
    }

    // Return cached value
    return cache[uri];
  }

  private string ProcessRequest(Uri uri)
  {
    var webClient = new WebClient();
    var reader = new StreamReader(webClient.OpenRead(uri));

    // Add result to cache
    return reader.ReadToEnd();
  }
}

That's better code, for a start, but it also shows that all we're doing is storing the return value from a method in a dictionary, using the parameter as a key. If we represent the method as a Func delegate, it will have the exact same type parameters as the dictionary used for the cache. So we can create a generic wrapper around the method which handles all the boilerplate code, and just concentrate on our application logic.

We could create a class which exposed a Func<T, TReturn> and held the dictionary internally, but there's a feature in C# (since 2.0) which means we don't even need to do that: closures. Closures mean that when you create an anonymous method, any locally-scoped variables referenced by the method are bundled up with it automatically by the compiler in a generated class that you don't have to worry about.

This means we can just create a new delegate wrapped around the method we want to cache, and bundle its dictionary in with it as a closure. We can do this using a utility method which will work with any method matching the Func<T,TResult> delegate signature:

public static class MethodCacher
{
  public static Func<T, TResult> MakeCached<T, TResult>(Func<T, TResult> func)
  {
    // cache and lockObject will be bundled into the delegate as closures.
    var cache = new Dictionary<T, TResult>();
    var lockObject = new object();

    Func<T, TResult> cachedFunc = (arg) =>
        {
          if (!cache.ContainsKey(arg)) // check for cached value
          {
            lock (lockObject) // exclusive lock
            {
              if (!cache.ContainsKey(arg)) // re-check after obtaining lock
              {
                cache.Add(arg, func(arg)); // add return value from wrapped delegate to cache
              }
            }
          }

          return cache[arg];
        };

    return cachedFunc;
  }
}

You can use this utility method to create caching versions of methods you don't have the source for, or to switch caching on and off easily, maybe depending on the quality of network connection. A full example of this use is available from my MSDN Code Gallery page.

I hope this has been interesting and/or informative, and I'll post something in a similar vein soonish.

Filed under: , , ,

Comments

No Comments

Leave a Comment

(required) 
(required) 
(optional)
(required) 
 
Page view counter