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.
- 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 beingnull
then the application is going to crash. - 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? - 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.