About Me

Hello, I'm an eclectic software professional working with enterprise software at SAP. This blog only contains my personal views, thoughts and opinions. It is not endorsed by SAP nor does it constitute any official communication of SAP.

Saturday, July 19, 2008

C# Extension Methods and Null References

With extension methods in C# 3.0, you can create methods that operate as if they were instance methods of other classes. You can "add" methods to any class, even to core classes like System.String and System.Object.

This example creates two string extensions:

public static class StringExtensions {
  public static string HtmlEncode(this string input) {
    return AntiXss.HtmlEncode(input);
  }
  public static int ToInt32(this string s) {
    return Int32.Parse(s);
  }
}

(I used the HtmlEncode method implemented in the Anti-Cross Site Scripting Library.)

The way to use extension methods is to call them as if they were instances of the extended class itself (given as the first parameter to the extension methods):

int input = "123".ToInt32();
...
string safeString = unsafeString.HtmlEncode();

Or you can use them as normal static methods (it has the same effect as above):

int input = StringExtensions.ToInt32("123");
...
string safeString = StringExtensions.HtmlEncode(unsafeString);

Extension methods are pure syntactic sugar. The compiler translates all extension method calls to calls to the corresponding static methods. I don't think it even qualifies as monkey patching. The syntax is convenient for the caller because he can pretend (or even believe) that the class has always had that method.

Now stop and think about null references. Normal instance methods can't be called with null references, they'll immediately throw a NullReferenceException. Extension methods can be called with null references (remember, they're just glorified static methods), and it's up to the developer to deal with them.

In short, the new syntax of extension methods can hide unintended semantic problems. The ToInt32 method will work as expected, because int.Parse will throw an ArgumentNullException (not a NullReferenceException, but still...) on null references. But the HtmlEncode method will actually return null, which might not be what's expected of it.

The developer that calls the extension method might not be the same that wrote it, so it's important for extension methods not only to look like instance methods, but to behave like them too. But, don't throw a NullReferenceException because it should be reserved for the runtime (it could add extra semantics to it). Instead, throw an ArgumentNullException when the instance parameter is null. Even in the ToInt32 method it should be done. It shows clear intentions and it's safer should the implementation change:

public static class StringExtensions {
  public static string HtmlEncode(this string input) {
    if (input == null) throw new ArgumentNullException();
    return AntiXss.HtmlEncode(input);
  }
  public static int ToInt32(this string s) {
    if (s == null) throw new ArgumentNullException();
    return Int32.Parse(s);
  }
}

It might make sense to break this rule if the extension method explicitly tests for null, and its name indicates it duly:

public static class StringNullExtensions {
  public static bool IsNullOrEmpty(this string s) {
    return string.IsNullOrEmpty(s);
  }
  public static bool IsNullOrBlank(this string s) {
    return s == null || s.Trim().Length == 0;
  }
}

Update: the ArgumentNullException is not ideal, so it would be interesting if C# (or the CLI) brought extensions methods even closer to instance methods, by automatically checking the instance parameter and throwing a NullReferenceException appropriately. A simple annotation would suffice. For example, we could use this! instead of this on methods that can't operate on null references:

  // NOTE: not valid C#

  public static string HtmlEncode(this! string input) ...

  public static bool IsNullOrBlank(this string s) ...

No comments:

Post a Comment