Guidelines for Method Overloading

Recently I was helping review some code for a bit of functionality being worked on in Community Server, and one of the items we discussed was how many method overloads should be provided for this particular piece of functionality. During that discussion I noticed a couple mistakes being made with the existing overloaded methods, and it got me thinking about API design from that perspective.

So what I want to do is review the guidelines for method overloading because this was certainly not the first time I’ve seen those mistakes being made. Being a fan of the Framework Design Guidelines book (I don’t have the 2nd Edition yet), I’m going to borrow its DO and DO NOT convention, expand upon some of its key points, and add a couple of my own.

Note that the code shown in this post isn’t necessarily what I was reviewing. There are certain aspects of that code in here, but the code shown below is not a copy-paste from Community Server.

DO use the same return type

This guideline isn’t specifically called out in the book, but I think it should be. Let’s say you have the following method overloads:

public User[] GetGroupMembers(int groupId)
public List<User> GetGroupMembers(string groupName)

Both of these methods return a set of users, but the first one does it with an array while the second one returns users in a generic list. The problem here is that the caller of these methods has to treat them differently. For example, if the caller wanted to get the number of users from those methods, this is what it looks like:

var numberOfUsers = GetGroupMembers(groupId).Length; // For the 1st method
var numberOfUsers = GetGroupMembers(groupName).Count; // For the 2nd method

I’ve yet to come across a situation where having different return types for overloaded methods made sense. Try to avoid it.

DO use the same parameter names

Here’s another one I’ve seen that really drives me nuts. It’s very perplexing why developers would use different names for the same parameter in overloaded methods. For example, I’ve seen stuff like this:

public List<User> GetGroupMembers(int groupId)
public List<User> GetGroupMembers(int id, int pageIndex, int pageSize)

Why in the world someone would choose to use “groupId” in one and just “id” in the other amazes me, yet I see it more than I should. Please keep your parameter names consistent across overloaded methods.

DO use the same parameter order

The book says it best in that “parameters with the same name should appear in the same position in all overloads”. That’s straightforward enough and seems like simple common sense, yet I’ve encountered overloaded methods like this:

public List<User> GetGroupMembers(int groupId)
public List<User> GetGroupMembers(int pageIndex, int pageSize, int groupId)

The problem with ignoring this guideline is that developers *expect* parameters of overloaded methods to be in the same position across all the overloads, so when you change it on them, not only does frustration creep in, but it opens the door for potential errors. For example, look again at the above overloads. The first parameter of the second method is an integer, just like in the first method. Yet in the second method it’s supposed to be the page index instead of the group ID, so if you mistakenly pass in the group ID as the first parameter in the second method, everything will still compile and run just fine, but the results are going to be wrong.

That being said, there are two exceptions to this guideline: a params array parameter and an out parameter. A params array can only be the last parameter in the parameter list, so you must take that into consideration when exposing them in method overloads. As for out parameters, they technically could go anywhere in the parameter list, but it’s generally accepted that they should appear last as well.

DO use call forwarding

This is another guideline that’s hinted at in the book but not specifically listed, so I wanted to do that here. The idea is that the shortest overload forwards the call onto the next biggest overload, and then that overload forwards the call to the next biggest overload, until finally all calls have been forwarded onto the largest overload, which actually does all the work. Take these method overloads as an example:

public List<User> GetGroupMembers(int groupId)
public List<User> GetGroupMembers(int groupId, int pageIndex, int pageSize)
public List<User> GetGroupMembers(int groupId, int pageIndex, int pageSize, SortOrder sortOrder)
public List<User> GetGroupMembers(GroupQuery groupQuery)

With these methods, the first one forwards onto the second one, which forwards onto the third one, which forwards onto the fourth one, which does all the work. Here’s what their implementations might look like:

public List<User> GetGroupMembers(int groupId)
{
    return GetGroupMembers(groupId, 0, 10);
}

public List<User> GetGroupMembers(int groupId, int pageIndex, int pageSize)
{
    return GetGroupMembers(groupId, pageIndex, pageSize, SortOrder.Ascending);
}

public List<User> GetGroupMembers(int groupId, int pageIndex, int pageSize, SortOrder sortOrder)
{
    var query = new GroupQuery();
    query.GroupID = groupId;
    query.PageIndex = pageIndex;
    query.PageSize = pageSize;
    query.SortOrder = sortOrder;

    return GetGroupMembers(query);
}

public List<User> GetGroupMembers(GroupQuery groupQuery)
{
    // Actual implementation to get group members goes here
}

Notice that even though the fourth overload has only one parameter, it’s considered the largest because the GroupQuery type contains many properties, thus it has the widest focus, while the other methods are much more narrow in their focus.

The nice thing about using call forwarding is that it saves you from writing potentially duplicate code. Instead of all four of the above methods making the same database call, only one of them does it. The other good thing about call forwarding is you can be sure all overloads exhibit the same behavior, whereas otherwise that might not be the case.

DO NOT over do it

This might be the last guideline in this list but it’s probably the most important. All too often I see developers go completely overboard with the number of overloaded methods they provide for a single piece of functionality. The mistake is that developers want to provide an overload for every possible parameter combination. Basically they try to be everything to everyone, when in fact that’s the wrong approach.

It really pays to use the 80/20 rule when designing an API with overloaded methods: provide 2 or 3 overloads that will take care of 80% of the callers and then provide another overload that takes care of the other 20%.

For instance, look again at the previous four overloads above, in particular the fourth method with the GroupQuery parameter. If you could see inside of it, you’d see that GroupQuery has 22 properties. Do you think it makes sense to have numerous overloads just to handle all the different combinations of those properties? No. And why not? Because 80% of the calls only care about the group ID, the page index and page size, and maybe the sort order. For the other 20% we provide the fourth overload that accepts the GroupQuery so that if a caller needs to set any of those other 18 properties, they can.

So be smart about how many overloads you provide on your API. If you find yourself with more than five, take a step back and really think about the 80/20 rule. In most cases you’ll realize you don’t need any more.