Tuesday 8 May 2012

C# - Method Overload Resolution

Some of even the most basic of concepts catch you out from time to time. I was working the other day and came across a problem where what was happening didn't immediately make much sense. So, I thought I'd post it up here as a reminder that even basic concepts of computer programming can leave you a little confused.

So, here's your overview of the problem... I had a piece of code that allowed me to run queries against a database. To add parameters to the query I had to write something like:


cursor.AddParameter("@ParameterName", "value");


All very straight forward so far. The problem arose because that piece of code can be run against either an Oracle database or a SQL Server database. These databases handle empty strings differently. In Oracle an empty string is treated exactly the same as a null value. In SQL Server an empty string is an empty string.

Anyway, I found a piece of code that read:


cursor.AddParameter("@ParameterName", string.Empty);


In this instance, the developer tested this on Oracle and didn't actually mean string.Empty. They meant null. But the code ran perfectly. The application was then hooked up to a SQL Server database and the query brought back the wrong records (actually, it didn't bring back any records at all). This was due to the difference in the Oracle and SQL Server databases.

So, an easy fix then. Change string.Empty to null and we're done. After all, that's what the original developer meant in the first place. Both Oracle and SQL server will handle it in the same manner and we're good to go. Or so I thought.

Here's the interface definition of ICursor (well, a stripped down definition at least) which is what our cursor variable in the above example is defined as:



public interface ICursor
{
    void AddParameter(string name, object value);

    void AddParameter(string name, Type type);
}



Who can see the problem?

The rules for resolving method overloads state that the method header with the most specific type match should be used. This makes perfect sense, if you had two methods defined: one that accepts an object and another that accepts a string. If you passed in a string then you'd expect the method defined with a string to be used.

However, null is a little special. It can match any reference type. Is the problem becoming more apparent now?

When we put string.Empty in as the second parameter, the string matches the first method, where the second parameter is an object. However, when we change the call to this:


cursor.AddParameter("@ParameterName", null);


the second method is now matched. The call does match the first method, null is a valid value for an object variable but it also matches the second method as well, as Type is a reference type. That is the most specific match and so that method is invoked.

Unfortunately, that second method does something entirely different and so my parameters weren't being mapped in my SQL query correctly and the application was falling over.

If I change the call so that I explicitly define the type of null to the lesser type, as below, then we have our solution.


cursor.AddParameter("@ParameterName", (object)null);


So, just a friendly reminder that even the fundamentals can catch you out from time to time!

As always, if you want to actually see this in action, I've knocked up a little demo solution which can be found here.

Have fun and happy coding!

No comments:

Post a Comment