Don’t Break My Mental Model – Beware of C# Extension Methods

I think extension methods is one of the things I love most in C# and is definitely missing in Java. For those that are not familiar with extension methods, they let you add methods to existing classes. You do this by creating a static method that receives the class you want to extend as the first parameters with the this keyword. It can be done for any class you can reference, even external libraries, even primitive types. Here’s an example of an extension method for a string:

public static class MyStringExtensions
{
    public static string ToCrazyCase(this string theString)
    {
        string result = "";
        for (int i = 0; i < theString.Length; i++)
            result += (i % 2 == 0 ? theString[i].ToString().ToUpper() : theString[i].ToString().ToLower());
        return result;
    }
}

var x = "hello".ToCrazyCase() // x is now "HeLlO";

Pretty cool, right?

Yes, it’s cool, but only if you respect the rules of the game. Notice that a string reference is passed to the method, and this reference can be null, but it is up to you to decide what to do when this happens. In my case, the behavior is as expected – if the method is called with a null string, the method will throw a NullReferenceException.

Now let’s say one of your friendly co-workers decided to write the method like this:

public static string ToCrazyCase(this string theString)
{
    if (theString == null || theString.Length == 0)
        return "";

    string result = "";
    for (int i = 0; i < theString.Length; i++)
        result += (i % 2 == 0 ? theString[i].ToString().ToUpper() : theString[i].ToString().ToLower());
    return result;
}

Nothing wrong with adding some null checks and returning a default value, right?

Wrong. Because it messes up our mental model on how null values are handled by the language. Let me explain what I mean by that.

In some part of a big codebase you are writing a function that uses ToCrazyCase, oblivious to how it handles null. Something like this:

public string MyStringManipulator(string s)
{
    // Do all kinds of stuff
    var z = s.ToCrazyCase();
    // Do more stuff that manipulates z
    return z;
}

When your code is running, MyStringManipulator starts returning empty strings… Which is weird because if s is null the assumption is that s.ToCrazyCase() will throw a NullReferenceException. But is doesn’t. And this here destroys a basic assumption that you have from the language: calling a method on a null reference will always throw an exception.

And now every time you use an extension method you must take this into account. And your mental model of the language is now broken and will take some time fixing.

There are ways to at least reduce the pain here, such as naming the method ToCrazyCaseNullSafe, but IMHO in this case there should be two functions (one that is null safe and one that isn’t). And it should also be used sparingly (I wrote a while ago about forcing the use of async, and my view hasn’t changed – don’t force me to change my mental model).

We have created a mental model of how the language works, and this mental model makes us productive. Please, don’t break this model.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.