P3.NET

Extension Method Guidelines Rebuffed

With the additional of extension methods developers have a powerful new way of extending existing types.  As extension methods become more and more common guidelines will be needed to ensure that extension methods “fit in” with the general design of the library.  If you are not aware of what an extension method is then the quick summary would be that it is a method that appears as an instance method of a type but is, in fact, defined in a separate static class.  This allows us to extend existing types without modifying the type itself.  It really becomes a powerful feature when applied to interfaces.  We can enhance an interface to expose a lot of new functionality while not requiring the interface implementer to do any extra work.  LINQ is a good example of this extensibility.  I personally tend to view extension methods more as static methods on a type  that be called using the instance-method syntax.  This, to me, gives a more accurate picture of what is happening and, as you will see, what is allowed.

Currently there are a lot of “guidelines” floating around that people like to mention.  Unfortunately it is still too early to determine which of these are truly best practices and which are opinions.  One guideline in particular bothers me the wrong way: extension methods should throw a NullReferenceException if the source is null.

Let’s start with a regular method as an example.  This method will return everything to the left of a particular string in another string.

static string Leftof ( string source, string value )
{
   if (String.IsNullOrEmpty(source))
      return “”;

   if (String.IsNullOrEmpty(value))
      return “”;
   …
}

This is a pretty handy function so it makes sense to make it an extension method so we might do this.

static class StringExtension
{
   public static string Leftof ( this string source, string value )
   {
      if (String.IsNullOrEmpty(source))
         return “”;

      if (String.IsNullOrEmpty(value))
         return “”;
      …
   }
}

So far so good but it violates the aforementioned rule about throwing an exception if a null is passed in.  The rationale is that an extension method appears to the developer as an instance method and should therefore behave like one.  While that is a reasonable argument I don’t think it applies in all cases.  Why do we have static methods on types, such as String.IsNullOrEmpty?  The general reason is because the method does not need an instance to perform its work.  But if the method itself considers null to be valid then the method would also need to be static.  So syntactically we cannot allow an instance method to accept null but static methods (and by definition extension methods) we can.

One of the big arguments against this approach is that “extension methods should look and act like instance methods”.  Why?  The only real reason I can see is because they look like instance methods when used but we, as developers, are use to the actual code being quite different than what we expect.  The assignment operator, for example, we would never expect to throw an exception yet it can if the type implements the assignment operator and does something inappropriate.  Addition, subtraction and other operators are the same way.    How about the relational operators? 

Data value1 = null;
Data value2 = null;
            
bool result = value1 < value2;

We would never expect the above code to throw an exception and yet a method call is occurring under the hood.  If that method throws an exception then so does the above code.  As developers we have to be aware that we don’t know everything that is going on under the hood so we should expect errors anywhere.

Another problem I have with this guideline is the solution.  The guideline is that extension methods should behave like instance methods but that simply isn’t possible.  If you call an instance method with a null reference then you will get a NullReferenceException.  This particular exception is one of a handful of system exceptions.  A system exception is an exception that is raised by the runtime proper.  Normal code, aka ours, should not throw system exceptions.  If you explicitly throw a system exception then code analysis tools like FxCop will generate CA warnings about it.  That is what you want.

Instead we have “application” exceptions.  Hence when we receive an argument that is null and we do not allow them then we throw ArgumentNullException.  So to make our extension method behave like an instance method we would need to either throw a system exception explicitly (which we aren’t suppose to do) or reference the source variable.  Here’s what we’d have to do to our earlier example to get it to throw the appropriate exception while still making code analysis happy.

public static string Leftof ( this string source, string value )
{
   //Force a NullReferenceException
   int len = source.Length;

   if (String.IsNullOrEmpty(value))
      return “”;

   …
}

I have a really big problem writing code just to force something to happen like throw a specific exception.  If I really wanted the exception I’d just throw ArgumentNullException.  But if I do that then I’m no longer making my extension method act like an instance method.  This becomes even more of an issue if you can short-circuit the method return based upon other argument values. 

A final argument for the guideline is that if an extension method eventually becomes an instance method then your application should behave the same but the reality is that you should have unit tests to verify your application’s behavior so a major change like going from extension to instance should be thoroughly tested anyway.  Here are my guidelines for extension methods.  Take them with a grain of salt.

  • Extension methods should be placed in a static class called TypeExtension.  For interfaces the “I” can be left off.
  • Extension classes should be only for extension methods and should not contain non-extension code.
  • Each extension method’s first parameter should be this Type source.
  • Use an extension method only if the source parameter makes sense and is used in the method.  Use a normal static method otherwise.
  • An extension method should not throw ArgumentNullException for source.    Other arguments are fine.  If source cannot be null then referencing the value will be sufficient to generate the correct exception.
  • If null is not necessarily an invalid value then do not throw any exceptions.  An extension method does not need to follow instance method semantics.
  • Document the behavior if source is null if it is not going to throw an exception.