Tuesday, July 28, 2009

On Method Overloading Best Practices

C# allows method overloading. An overload is defined as a method that has the same name as another method, and differs only in the number and/or type of parameters it receives. (Technically, .NET allows creation of overloads based only on the return type but this construct is not allowed in C#).

The .NET Framework uses method overloading with several of it's methods. For example, the Activator.CreateInstance method has no fewer than 14 distinct overloads for it's usage. Each of these overloads provides the developer flexibility with how he/she chooses to call this method, and can greatly abstract out some of the more menial tasks that may have to be done by the developer, such as casting from one type to another before making a call, or having to research default parameters for a method in order to ensure the proper parameters are passed into the method.

When to Use Method Overloading

Method overloading should be used to allow your callers greater type and parameter options when calling a specific method. Good uses of method overloading are as follows:

Default values

When a method has parameters for which "acceptable defaults" can be defined, an overload of that method that omits the defaulted parameter can be created which would subsequently call the method that contains the parameter for which there is a default, passing in the default value. An example of this might be the Int32.TryParse method, which has two overloads:

bool TryParse(string s, out int result);
bool TryParse(string s, NumberStyles style, IFormatProvider provider, out int result);

Both of these methods ultimately route to the same method, namely the internal static method Number.TryParseInt32. The difference is that the first one passes NumberStyles.Integer and NumberFormatInfo.CurrentInfo as the default values for style and provider, whereas the second overload passes the user defined values. Therefore, the first one is provided for convenience for default values.

Type conversion convenience

If a piece of data can be represented using multiple types, then overloads can be created to abstract type conversions away from the client. An example might be a fictional "CreateInstance" method.

T CreateInstance();
object CreateInstance(Type type);
object CreateInstance(string typeName);

These three methods all accomplish the same purpose, namely, creating an instance from a representation of a type. Two of them are overloads. In the first method, the representation is passed via a generic type parameter, T. The next method passes the type via an instance of the Type class, and the third method (which is an overload of the second method) passes the type via it's full name. It's important to note that these three parameter types are intrinsically the same piece of data only represented in different ways. They all three represent a single type, which is then used to create an instance. If I wanted to then create an instance of a class based on another piece of data other than the type, I should not create an overload, but should create a new method with a new method name.

When Not to Use Overloading

There are a couple of instances where method overloads would not be appropriate.

To clean up Intellisense

Designing anything within an object model simply to cater to ease of use when it comes to a specific technology is almost always a mistake. Cleaning up Intellisense is not related to having a clear code base, and catering to Intellisense in this situation can muddle the true purpose of your methods.

To take advantage of the type differences of intrinsically different pieces of data

An example of this could be overloads like the following:

Product GetProduct(Guid id);
Product GetProduct(string name);

The first issue here would be that the name of the methods, GetProduct, does not accurately or completely describe the actions that the method would take in order to accomplish it's goal. In both situations, a Product instance is retrieved, but both situations accomplish this task based on intrinsically different data. An id is not a name, and cannot be converted to a name without having more information. Creating this kind of overload makes compiler resolutions dependent upon whatever type the data happens to be at the moment.

Also, potential inconsistencies in naming can occur in the situation where a third overload is to be added. Suppose there is a client reference number for each product, and a third overload needs to be created to manage fetching products by this number. Suppose, furthermore, that the client reference number is of the type string. The preferred overload would look like this:

Product GetProduct(string clientRef);

Unfortunately, since overloads are only distinguished based on the name and the incoming parameter types, the above method would cause a compiler error when combined with the previous two methods, leaving the developer with only two choices: rename all the methods for consistency, which would also force callers to change their code, or else use a different name for the new method, causing an inconsistency, and learn to deal with it. Of course, either option is no good.

In the end, the best option is to only use method overloading when the methods to be overloaded accomplish the exact same task given the same data where portions of the data either have acceptable default values, or else are already the same bit of data only represented in different formats.