Null Instance Extension Methods

Checking for null can be time consuming and add a lot of extra noise to the code you write. In some ways it can reduce the readability of your code and obscure the purpose of your functions.

For example, check out all of the null checks required for this bit of code.

public static int GetVersion() {

  //check if this exists
  if (!File.Exists(PATH_APP_DETAILS)) 
        return DEFAULT_VERSION;

  //try and load the document
  XDocument document = null;
  try {
    document = XDocument.Load(PATH_APP_DETAILS);
  }
  catch {
    return DEFAULT_VERSION;
  }
  if (document == null) { return DEFAULT_VERSION; }

  //find the element
  XElement release = document.XPathSelectElement("app/release");
  if (release == null) return DEFAULT_VERSION;

  //get the value
  XAttribute version = release.Attribute("version");
  if (version == null) return DEFAULT_VERSION;

  //convert the version
  int current = int.TryParse(version.Value, out current) 
        ? current 
        : DEFAULT_VERSION;
  return current;

}

Yikes... that is a lot of code for such a small task but what else can you do? You can't just call methods on something that is null or your app is going to blow up... or can you?

Null Instance Extension Methods

Extension methods feel magical the first time you see them in action but ultimately they are just static methods that can be invoked from anywhere in your project.

But here is the neat thing -- Since an extension method isn't tied to the instance of a class then they can be invoked without an instance of the class.

Sound confusing? Here is an example of what I mean...

//create an extension method
public static class StringExtensions {
  public static void Test(this string value) {
    Console.WriteLine("Hello from a null test!");
  }
}

//and then call it
string nothing = null;
nothing.Test(); //ok!
nothing.ToUpper(); //crash

You'll notice that the call to Test worked fine - If you put a break point in the method and check the value argument then you'll notice it is actually null! We were able to call the method Test even though there wasn't an instance of a string.

So how can this help us? Well let's take the same XML problem from earlier in the post and create some extension methods that don't care if the instance being invoked is null or not.

//helper extension methods for XML documents
public static class XmlExtensions {

  //loads an xml document to use
  public static XDocument LoadDocument(this XDocument document, string path) {
    if (!File.Exists(path)) return null;
    try { return XDocument.Load(path); }
    catch { return null; }
  }

  //uses an XPath to find an element
  public static XElement GetElement(this XDocument document, string path) {
    return document is XDocument 
            ? document.XPathSelectElement(path) 
            : null;
  }

  //returns the value of an attribute
  public static string GetAttribute(this XElement element, string name) {
    XAttribute attribute = element is XElement ? element.Attribute(name) : null;
    return attribute is XAttribute ? attribute.Value : string.Empty;
  }
}

Now let's take a look at how we can solve the same problem as before but with these new extension methods...

public static int GetVersion() {

  //open the document (Mono)
  XDocument document = document.LoadDocument(PATH_APP_DETAILS);

  //It appears you have to do it like this in Visual Studio
  //XDocument document = ((XDocument)null).LoadDocument(PATH_APP_DETAILS);

  //find the element
  XElement release = document.GetElement("app/release");

  //get the version
  string version = release.GetAttribute("version");

  //convert and return the value
  int current = int.TryParse(version, out current) 
        ? current 
        : DEFAULT_VERSION;
  return current;
}

As far as I can tell, no matter what you throw at this code, you will either get the default version or the value in the XML document. This definitely reduces the lines of code count... but the clarity gains might be debatable.

  1. A developer modifying this code might not realize the value could be null or could be a real object. If they call an instance method and the value ends up being null then the application is going to crash.
  2. Some developers are going to scratch their heads when they read the line XDocument document = document.LoadDocument(...). Invoking a method on a value you're declaring and should be null and returning the result it to itself? Huh?
  3. The more forgiving your code is then the more prone to external errors it will be, which in turn will introduce subtle runtime errors in your applications. Sometimes it's a better idea just to throw an exception and keep the program from running.

So, while this approach has benefits it also has pitfalls. Make sure to evaluate the risks before using something like this in your code. If implemented carefully this could reduce complexity in your application...

...or at least make for a neat parlor trick the next time you're with your dev team.

August 22, 2010

Null Instance Extension Methods

Use extension methods even with a null instance of a class.