DEV Community

Cover image for C# OOP: Method Overloading
Adam Romig 🇵🇭
Adam Romig 🇵🇭

Posted on • Updated on • Originally published at romig.dev

C# OOP: Method Overloading

Jump to:

What is overloading?

Overloading usually refers to the ability to have multiple definitions for the same function call. We can use overloading to execute the same task but with slightly different parameters, usually of different data types. Implementing a method this way can eliminate unneeded complexity and make code easier to read.

Method Overloading

Method overloading is actually a form of polymorphism. In C#, methods can have different method signatures, which means method has multiple definitions with the same name but different parameters that it can accept. Not only different data types, but also number of parameters allowed and their order.

  • Overloaded methods are differentiated by the number and type of parameters it accepts. A couple of examples:
    • FunMethod(int param1) + FunMethod(int param1, int param2)
    • FunMethod(int param1) + FunMethod(string param1)
  • A method cannot be defined with the exact same name, order, and types more than once. Each overload needs to be different in some way.
  • Return types are not considered while differentiating overloaded methods. That is, two overloaded methods cannot have the same signature but different return types. The methods can have different return values, but ultimately it's the parameter list that matters.

A simple example:

static int Multiply(int a, int b)
{
  int product = a * b;
  return product;
}

static int Multiply(int a, int b, int c)
{
  int product = a * b * c;
  return product;
}

static double Multiply(double a, double b, double c, double d)
{
  double product = a * b * c * d;
  return product;
}

public static void Main()
{
  int product_one = Multiply(4, 5);
  int product_two = Multiply(4, 5, 6);
  double product_three = Multiply(10, 3.14, 5.25, 4);

  Console.WriteLine("One Method, Different Uses\n");
  Console.WriteLine("Multiplying 2 integers : {0}", product_one);
  Console.WriteLine("Multiplying 3 integers : {0}", product_two);
  Console.WriteLine("Multiplying 4 doubles  : {0}", product_three);
}
Enter fullscreen mode Exit fullscreen mode

Output:

One Method, Different Uses

Multiplying 2 integers : 20
Multiplying 3 integers : 120
Multiplying 4 doubles  : 659.4
Enter fullscreen mode Exit fullscreen mode

→ dotnetfiddle

Type Promotion

So our simple example handles integers and doubles but what if we wanted to pass a different data type as one of the parameters, say a float?

We can just add an overload for that as well, easy peasy.

static float Multiply(float a, float b)
{
  float product = a * b;
  return product;
}
Enter fullscreen mode Exit fullscreen mode

What if... we didn't? Technically we can do just that. Type promotion happens when a lesser data type can be upgraded to a bigger one. For example: a short can be promoted an int, an int to a long, a float to a double.

static int Multiply(int a, int b)
{
  int product = a * b;
  Console.WriteLine("Called the \"int\" method");
  return product;
}

static double Multiply(double a, double b)
{
  double product = a * b;
  Console.WriteLine("Called the \"double\" method");
  return product;
}

public static void Main()
{   
  Console.WriteLine("Multiply with integers: {0}", Multiply(3, 9));
  Console.WriteLine("Multiply with doubles: {0}", Multiply(3.14, 5));
  Console.WriteLine("Multiply with floats: {0}", Multiply(3.14f, 5));
}
Enter fullscreen mode Exit fullscreen mode

Output:

Called the "int" method
Multiply with integers: 27
Called the "double" method
Multiply with doubles: 15.7
Called the "double" method
Multiply with floats: 15.7000005245209
Enter fullscreen mode Exit fullscreen mode

→ dotnetfiddle

Even though our code only has overloads for types int and double, the line with the float (3.14f - the f denotes a float value) runs just fine. And it does so by utilizing the "double" overload. Even the int, 5, got promoted. Come to think of it, the second parameter for the method call in the "Multiply with doubles" line is technically an integer, not a double. It was type promoting even in the first example!

Type promotion doesn't always happen. If there is an overload for the exact data types that are used when calling the method, the program will choose that one first.

Resource: Implicit numeric conversion table @ Microsoft

Optional Parameters

A very useful benefit of method overloading is with optional parameters. We create an optional parameter by declaring a default value with the variable name.

static int Multiply(int a, int b = 0)
{
  int product = a * b;
Console.WriteLine("Called Multiply(int, int)");
return product;
}

static double Multiply(double a, double b = 5.5)
{
  double product = a * b;
Console.WriteLine("Called Multiply(double, double)");
  return product;
}

public static void Main()
{
  Console.WriteLine("Passing one int argument   : {0}", Multiply(4));
  Console.WriteLine("Passing two int arguments  : {0}", Multiply(4, 5));
Console.WriteLine("Passing one double argument: {0}", Multiply(4.0));
}
Enter fullscreen mode Exit fullscreen mode

Output:

Called Multiply(int, int)
Passing one int argument   : 0
Called Multiply(int, int)
Passing two int arguments  : 20
Called Multiply(double, double)
Passing one double argument: 22
Enter fullscreen mode Exit fullscreen mode

→ dotnetfiddle

Nothing really different in the rules of how it picks the overloaded method, just that the second argument is not needed to successfully call it. But when we do supply it, the default value is ignored.

Named Arguments

Another feature that can be used to work with overloaded methods is named arguments. Calling the method is the same except that the argument supplied is prefixed with the variable name in the parameter list.

static int Square(int a)
{
  int square = a * a;
Console.WriteLine("Called Square(int a)");
return square;
}

static double Square(double b)
{
  double square = b * b;
Console.WriteLine("Called Square(double b)");
  return square;
}

public static void Main()
{
  Console.Write("Square of 9 is {0}", Square(b: 9));
}
Enter fullscreen mode Exit fullscreen mode

Output:

Called Square(double b)
Square of 9 is 81
Enter fullscreen mode Exit fullscreen mode

→ dotnetfiddle

Even though an integer was passed as the argument to the Square method, the double overload was called because we called it with b: as a named argument. If we had used a:, the integer overload would have been used.

Overloading and Inheritance

Using method overloading along with class inheritance can be confusing sometimes. Overloads are handled by considering the class of the "target" of the method call and checking the methods that belong to it first. If there isn't a suitable method among that class, consideration then goes to its parent class. And then to that class's parent class and so on, outwards. It won't look at the target class's children.

class Parent
{
  public void Method(int x)
  {
    Console.WriteLine("Parent's version of Method");
  }   
}

class Child : Parent
{
  public void Method(double y)
  {
    Console.WriteLine("Child's version of Method");
  }
}

public class Program
{
  public static void Main()
  {
    Child c = new Child();
    c.Method(10);
  }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Child's version of Method
Enter fullscreen mode Exit fullscreen mode

→ dotnetfiddle

Above the target of the method is c which is of the Child class. With only method (and an applicable one because of type promotion), there's no need to look at the Parent class's method.

C# does this on purpose to help avoid a fundamental problem with object-oriented programming called the brittle base class problem. The idea is that base classes can be considered fragile because seemingly small changes to them can cause unexpected and sometimes drastic problems in derived classes. There are a number of things that languages have done to help prevent these types of problems but it's something to be aware of when designing classes. This includes overloading methods across classes.

Additional weirdness incoming.

So let's change the base class method to a virtual method so we can add an override method of Method(int x). Now when we call c.Method(10);, it's still passing an integer type as before. So it will call the new override method, right?

class Parent
{
  public virtual void Method(int x)
  {
    Console.WriteLine("Parent's version of Method");
  }   
}

class Child : Parent
{
  public override void Method(int x)
  {
    Console.WriteLine("Child's override version of Method(int)");
  }
  public void Method(double y)
  {
    Console.WriteLine("Child's version of Method");
  }
}

public class Program
{
  public static void Main()
  {
    Child c = new Child();
    c.Method(10);
  }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Child's version of Method
Enter fullscreen mode Exit fullscreen mode

→ dotnetfiddle

Nope, it does not! In C# if you override a base class method, it doesn't count as a declaration towards overloaded methods. Try editing out the override keyword from the method declaration. Now it does what we thought it would do.

Confused any? It may be a good option to avoid overloading methods across inheritance lines and keep it within the target class.

Interesting reads: Eric Lippert's articles about the Brittle Base Class


Wow, that's a doozy.

Hopefully you can see how overloading methods can be useful even with all of the rules and potential quirkiness. My advice would be to keep the structure and use of overloaded methods as simple as possible.

A possible idea for good overloads would be for class constructors, either through overloading the constructor itself or a separate static method that can create the new instances in the background (keeping construction as well-defined limited processes).

Another is for various helper functions that will accept different types of arguments. For example, I've written an error logging method that either takes an Exception object or a plain text string in case the Exception was not available.

Or sometimes it may get a bit complicated. Don't be afraid to take a step back. You may find that overloading isn't the best option and just create the other method as a separate stand-alone one.

With a lot of things in technology, I tend to opt for the simplest solutions. Next article, though, we'll discuss more on overloading but this time with operators.

Top comments (0)