Extension methods are great, they allow you to “add” behavior to sealed classes. Even if the class is not sealed, but it’s a external dependency, you can do that.

Of course, don’t be fooled, extension methods are nothing more than a syntactic sugar for static helper methods. Note that, you can write extensions method for primitive types, as well:

Sample usage

public static class IntExtensions
{
    public static int ToMilliseconds(this int seconds)
    {
        return seconds * 1000;
    }
}

And here it is the sample usage:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(5.ToMilliseconds()); // 5000
    }
}

It may not be the most useful method, even more, it won’t work well with big enough values, but that’s beside the point of this post.* The good thing is you get a fluent syntax. It’s more obvious what value you are processing. As I mentioned before, you can use it as a regular static method, as so:

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(5.ToMilliseconds()); // 5000
            Console.WriteLine(IntExtensions.ToMilliseconds(5)); // 5000
        }
    }

Now, it’s not that bad, but it’s not better either. You just need to be more verbose, specify the class name, in our case IntExtensions and then pass the arg to the method.

Checking if method is an extension method

If you are writing .NET, you are probably using Visual Studio or maybe VS Code. Personally, I use both, but I don’t use VS Code for writing anything .NET related.

Visual tip when IntelliSense shows available methods:

IntelliSense Arrow

Text when IntelliSense shows method signature

IntelliSense Text

Beside these visual tips, you just call an extension method as a class’ instance methods.

What you can’t do with extension methods?

As good as they sound, you can’t access private members or more correctly stated, members you don’t have access outside of the class itself. This basically means, you can’t just mutate the internal state of an object.

How extensions methods can bite you?

I was working with a code that was using List’s AddRange(collection) method. Nothing too fancy. A sample code:

class ListProcessor
{
    private readonly IList<int> list = new List<int>();

    public IList<int> List
    {
        get
        {
            return this.list;
        }
    }

    public void UpdateList()
    {
        this.list.AddRange(new int[] { 1, 2, 3 });
    }
}

Now, the attentive reader may shout out: “Hey, this won’t even compile!” and of course, they would be right, unless I’ve written my own IList<T> interface (I haven’t) or if AddRange(collection) extension method was written by someone else. Well, as you’ve probably guessed from the post’s title, it was the latter. The extension method looked something similar to:

public static class IListExtensions
{
    public static void AddRange<T>(this IList<T> list, IEnumerable<T> collection)
    {
        foreach (var item in collection)
        {
            list.Add(item);
        }
    }
}

And was used in the context of ObservableCollection<T> when multiple items needed to be added to it, so instead of writing foreach every time, a method was added. Now, a rule of thumb in C# is to return a more general type (e.g. IList<T>, ICollection<T>, etc). The addition to this rule is to accept the most specific arg needed to do the job.

However, by introducing this extension method we’ve actually messed up with the List<T> class AddRange(collection) method, because the way we declared our list: private readonly IList<int> list = new List<int>();.

You can check that the IList<T> implementation (no AddRange(collection) there).

Now, this is a naive implementation, mainly used for adding 10-20 elements to a collection binded in WPF’s UI. So this won’t really hurt performance, however if the code should perform fast, you would definitely see performance hit.

How could’ve this be avoided?

  1. Declare our private field as List<int> everything would be fine.
  2. The extension method was expecting ObservableCollection<T> and not IList<T>

Conclusion

Do not try to be smart by predicting a how a method will be used in the future! You can always fix it later, after all, it’s in your source code.

* Also, we are assuming the int value represents a second (may be an minute, hour or a lunar month?!), which is another problem by itself. :)