1. Introduction
In real life, a lot of actions are like Swiss Army knives: the same command can work with different sets of tools. For example, imagine an ATM: if you insert a card, the ATM asks for a PIN; if you enter a phone number, it waits for a confirmation code from SMS. It's the same action—"verify user"—but the ways are different.
In programming, we often run into a similar situation: you need to do what seems like one operation, but the data might be different types or have a different number of parameters. For example, your method should greet a person or an animal, or sum two, three, or even ten integers.
Sure, you could name your methods something like SumTwo, SumThree, SumArray. But programmers are lazy (they say laziness is the engine of progress for a reason). Plus, that makes your code less readable.
Method Overloading
Method overloading is a way to make the same method work with different sets of parameters, while keeping the method name the same. It's a form of polymorphism, but not the inheritance kind.
Method overloading means you can create multiple methods with the same name in one class (or struct), but with different parameter lists (by type, count, and/or order).
Method Signature
A method's signature in C# is its name plus the type(s) and order of its parameters. The return type is NOT part of the signature! This often leads to some unexpected errors (I'll talk about those in a bit).
2. Overloading in Action: Simple Examples
Let's make a Greeter class that greets users in different ways: just by name, by name and age, or with no parameters at all.
public class Greeter
{
// Greeting with no parameters
public void Greet()
{
Console.WriteLine("Hello, world!");
}
// Greeting with a name
public void Greet(string name)
{
Console.WriteLine($"Hello, {name}!");
}
// Greeting with a name and age
public void Greet(string name, int age)
{
Console.WriteLine($"Hello, {name}! You're already {age} years old? Not bad!");
}
}
Now you can call any of these methods, and the C# compiler will pick the right one—based on the number and types of parameters you pass.
var greeter = new Greeter();
greeter.Greet(); // Hello, world!
greeter.Greet("Anya"); // Hello, Anya!
greeter.Greet("Pyotr", 23); // Hello, Pyotr! You're already 23 years old? Not bad!
3. Difference by Type and Number of Parameters
Overloading works if methods differ by:
- the number of parameters,
- the type of at least one parameter,
- the order of parameter types (but be careful here).
Let's try adding another overload that only takes age:
public void Greet(int age)
{
Console.WriteLine($"That many years—pretty cool! ({age} years)");
}
Now the calls:
greeter.Greet(10); // That many years—pretty cool! (10 years)
Important to remember: if methods only differ by return type, you can't overload them. For example, this code will throw an error:
// Compilation error!
public int Foo(string s) { ... }
public double Foo(string s) { ... }
The compiler will complain: "Method Foo(string) is already defined, give me something trickier!"
4. Overloading and the C# Standard Library
Overloading isn't just for our made-up Greet. Let's check out the .NET docs for Console.WriteLine:
| Signature | Purpose |
|---|---|
|
Prints an empty line |
|
Prints a string |
|
Prints an integer |
|
Prints a floating-point number |
|
Formats a string with one argument |
|
Formats with multiple arguments |
These are all overloads of the same method—WriteLine. Now you get why you can always do:
Console.WriteLine("Just a string");
Console.WriteLine(123);
Console.WriteLine(2.5);
Console.WriteLine("Sum: {0}", 42);
And the compiler always figures out your call!
5. How Does the Compiler Pick Which Overload to Call?
It's strict: it looks at the types and number of arguments you actually pass. Here's a little table for clarity:
| Call | Which version runs? |
|---|---|
|
|
|
|
What Happens If It's Ambiguous?
Sometimes things get out of hand. Here's an example of ambiguous overloading—the compiler can't pick the right version
public void Print(int a, double b) { ... }
public void Print(double a, int b) { ... }
printer.Print(5, 10);
// Error: ambiguity—which Print to call? (both kinda fit)
The compiler will throw an ambiguity error. In these cases, it's better to avoid overloads with the same number of parameters and similar types, when it might confuse the compiler.
6. params — Variable Number of Parameters
Say you want to make a method that takes any number of numbers. The params keyword is your friend.
public void SumAll(params int[] numbers)
{
int sum = 0;
foreach (int n in numbers)
sum += n;
Console.WriteLine($"Sum: {sum}");
}
Now you can call:
SumAll(1, 2, 3); // Sum: 6
SumAll(10, 20); // Sum: 30
SumAll(); // Sum: 0
You can combine params methods with overloading, but the main thing is not to make overloads that will confuse the compiler about which version you meant.
7. Overloading and Parameter Modifiers (ref, out, in)
C# treats methods as different if their parameter modifiers differ (so void Foo(int a) is different from void Foo(ref int a), and both can exist in one class):
public void SetValue(int a)
{
a = 42;
}
public void SetValue(ref int a)
{
a = 100;
}
A call without ref goes to the first version, with ref—to the second:
int n = 5;
SetValue(n); // n stays 5 (value is copied)
SetValue(ref n); // n becomes 100
8. Diagram: What Is Overloading
+----------+
| MyClass |
+----------+
|
| (method fragment)
+-----------------------+
| void Foo() |
| void Foo(int a) |
| void Foo(string s) |
| void Foo(int a, int b) |
+-----------------------+
Or, in code:
// Calling overloaded versions of Foo():
var mc = new MyClass();
mc.Foo(); // void Foo()
mc.Foo(5); // void Foo(int)
mc.Foo("Hello"); // void Foo(string)
mc.Foo(2, 3); // void Foo(int, int)
9. Example: Overloading Methods in Our App
Let's keep building our learning app by adding method overloading to the animal hierarchy.
public class Animal
{
public string Name { get; set; }
// Method to make a sound
public virtual void MakeSound()
{
Console.WriteLine("Some weird sound...");
}
// Overloaded method: sound with specified volume
public void MakeSound(int volume)
{
Console.WriteLine($"Animal makes a sound at {volume} dB.");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Woof!");
}
// Overloaded method: woof with volume
public void MakeSound(int volume)
{
Console.WriteLine($"Woof! (volume: {volume} dB)");
}
}
Try these calls:
Dog rex = new Dog();
rex.MakeSound(); // Woof!
rex.MakeSound(75); // Woof! (volume: 75 dB)
Note: in the child class (Dog) we overloaded the MakeSound(int volume) method, so now it has both versions: with and without a parameter.
10. Typical Mistakes with Method Overloading
Mistake #1: Trying to overload just by return type.
That's not possible—the return type isn't part of the method signature. Overloading has to differ by the number or types of input parameters, not by void or int.
Mistake #2: Ambiguous overloads that confuse the compiler.
Overloads with the same number of parameters and similar types (like int and double) can trip up the compiler. Example: Print(int a, double b) and Print(double a, int b)—calling Print(1, 1) will throw an ambiguity error.
Mistake #3: params conflicts with other overloads.
A method with params can grab a call meant for another overload. If the types match, the compiler might pick a method you didn't expect.
Mistake #4: Forgetting that ref and out are part of the signature.
Methods Do(ref int x) and Do(out int x) are considered different overloads. If you don't keep that in mind, it's easy to get confused and call the wrong version.
GO TO FULL VERSION