Professor Mustard Programming
 

Programming 101 - Unit 03 - Modularity

03b) Overloading
 
"Overloading", in programming terms, has very little to do with sending an enormous shock through your computer that blows out every circuit in its tiny silicon brain. In programming terms, it usually means that you are expanding on something that you've already defined. Let's take a look at a simple "overloaded" method (I've omitted the actual code in the methods, since we're only interested in the signatures right now):

static int GetInt(string prompt)
{
}

static int GetInt(string prompt, int min, int max)
{
}



You might remember that we previously wrote a GetInt() method to simplify the process of getting integers from the console. Well, now we've written a second GetInt() method, only this one takes different parameters... thus, we have "overloaded" the GetInt() method.

Your first response to this amazing mechanism is probably, "So what?". Well, the original method worked well, but it didn't account for the fact that the programmer might also want to limit the range of input that the user could enter (for example, a number between 1 and 10). We could just add a "min" and "max" parameter to the original GetInt() method, but then we'd have to change every call to GetInt() that we ever made, so that each call now passed in a maximum and minimum. Besides, what if we don't want to put a maximum or minimum limit on our return value? We could pass in int.MinValue and int.MaxValue every time we call the method, but do we really want to? No, we don't.

Overloading allows us to define as many different copies of a method as we want. As long as the method signature is different in each case, C# will never complain about duplicate methods existing in our program. Please note, however, that changing the return type of the method does not count as changing the signature. If you have two methods with the same name and parameters, but different return types, C# will refuse to compile it. This is because the program would have no way of knowing which method you actually wanted to use when you called it (C# can't assume that you want the "int" version of a method, just because you happen to be assigning the method's return value to an "int" variable).

Another benefit of overloading is code reuse. Just because we've defined a second GetInt() method doesn't mean that we actually have to re-write the entire thing. We can put the complete set of code in GetInt(string, int, int), since it takes in more information. The original, simpler GetInt(string) only needs to call the more complicated one:

static int GetInt(string prompt)
{
  return GetInt(prompt, int.MinValue, int.MaxValue);
}



...so we're still technically passing in three parameters every time we call GetInt(), but the original caller doesn't have to know about those second two parameters if they're calling the simpler one. We could even make a GetInt() that has no parameters, and uses a generic prompt:

static int GetInt()
{
  return GetInt("Please enter an integer: ", int.MinValue, int.MaxValue);
}



In both cases, we are using simpler methods to call a more complex one, by providing "default" value parameters that the original caller never has to see or worry about. Of course, you can also write an overloaded method's code from scratch. It may be that fewer parameters allows for a smaller, more efficient block of code, or even demands a completely different approach. In general, however, overloaded methods can get by with a single call to their more complicated cousins.

Having said all that, when do we actually use overloading? If you're just coding a simple or "single-use" application, you should only use it when you need to. There's no need to write dozens of overloads for every method, in an attempt to cover every possible combination of parameters you'll ever need. Indeed, for simple or "single-use" applications, you should avoid unnecessary code like the plague, to reduce "copy and paste" errors and other unsavory glitches. Simply put, you should write overloads as you need them, and not just for the sake of having them.

Libraries, or code that you intend to share between different programs, work a little differently. Because libraries may be used in a wide variety of circumstances, for a wide variety of purposes, you should try to provide overloads for any likely circumstance. This still doesn't mean covering every possible combination of parameters, but it does mean that you should try to anticipate the various different ways that programmers (including yourself) will want to use your methods, and write overloads for them accordingly. For important (or frequently used) methods, a good standard is to provide the following overloads: an overload with the maximum number of parameters, an overload with a minimal amount of parameters, and one or two specific scenarios. As before, though, don't write overloads just for the sake of having them. Only provide overloads that programmers will actually want to use.

The code sample below offers an extreme example of overloading, where the PrintLine() method has been provided with many, many different parameter combinations. It helps drive home how overloading can work, but in practical terms, one or two overloads probably would have been plenty.



using System;

namespace B_Overloading
{
  class Class1
  {
    [STAThread]
    static void Main(string[] args)
    {
      PrintLine(".oOo", "<-", "->", 50);
      PrintLine("=", "[", "]");
      PrintLine("-", "*");
      PrintLine("^", 60);
      PrintLine("=");
      PrintLine(35);

      PromptForExit();
    }

    // "Primary" method with all the parameters
    static void PrintLine(string linePattern, string startPattern, string endPattern, int length)
    {
      int endsLength = startPattern.Length + endPattern.Length;
      string midString = "";
      while( (midString.Length + endsLength) < length )
      {
        midString += linePattern;
      }
      midString = midString.Substring(0, length - endsLength);

      string lineString = startPattern + midString + endPattern;
      lineString = lineString.PadRight(80, ' ');
      Console.Write(lineString);
    }

    // "Lesser" method: Assume length of 80
    static void PrintLine(string linePattern, string startPattern, string endPattern)
    {
      PrintLine(linePattern, startPattern, endPattern, 80);
    }

    // "Lesser" method: Assume length of 80, and matching start/end patterns
    static void PrintLine(string linePattern, string endPattern)
    {
      PrintLine(linePattern, endPattern, endPattern, 80);
    }

    // "Lesser" method: Assume that all patterns match
    static void PrintLine(string linePattern, int length)
    {
      PrintLine(linePattern, linePattern, linePattern, length);
    }

    // "Lesser" method: Assume length of 80, and that all patterns match
    static void PrintLine(string linePattern)
    {
      PrintLine(linePattern, linePattern, linePattern, 80);
    }

    // "Lesser" method: Assume that all patterns are "="
    static void PrintLine(int length)
    {
      PrintLine("=", "=", "=", length);
    }

    // "Lesser" method: Assume length of 80, and that all patterns are "="
    static void PrintLine()
    {
      PrintLine("=", "=", "=", 80);
    }

    static void PromptForExit()
    {
      Console.Write(Environment.NewLine + "Program complete! Hit enter to exit...");
      Console.ReadLine();
    }
  }
}