1. Introduction
Let's say we have a Person class:
public class Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
And you feel almost like a genius — you can create people with any name and age. But here's the catch: sometimes you just don't have enough info! The app user only entered a name, and the age will be known later for some reason. Or maybe you want to set a "default" age sometimes. Sure, you could hack around it, but C# gives you a slick solution — constructor overloading.
Constructor overloading means that a class can have multiple constructors with the same name (the class name), but with different parameter lists.
What's the difference between these lists? They can differ by:
- Number of parameters: For example, one constructor takes 2 parameters, another takes 3.
- Parameter types: One takes (string, int), another — (string, decimal).
- Parameter order: (string, int) and (int, string) are different signatures.
The most important rule: the compiler tells constructors (and any methods) apart by their signature. The signature includes the constructor (or method) name and its parameter list (number, types, and order). Access modifiers (public, private) and, for regular methods, the return type, are not part of the signature. But constructors don't have a return type, and the name always matches the class name.
2. Constructor Overloading: Syntax and Example
public class Cat
{
public string Name;
public string Color;
// Constructor with no parameters
public Cat()
{
Name = "Nameless cat";
Color = "Gray";
}
// Constructor with one parameter
public Cat(string name)
{
Name = name;
Color = "Gray";
}
// Constructor with two parameters
public Cat(string name, string color)
{
Name = name;
Color = color;
}
}
Usage:
Cat barsik = new Cat(); // "Nameless cat", "Gray"
Cat murzik = new Cat("Murzik"); // "Murzik", "Gray"
Cat ryzhik = new Cat("Ryzhik", "Ginger"); // "Ryzhik", "Ginger"
If you specify parameters when creating an object, C# will "guess" the right constructor by their number and type.
Super handy! The user of your class doesn't have to specify parameters they don't know: if they just want a cat — use the no-parameter constructor, if they know the name — use the other one, and so on. Want full control over the color — grab the third option.
3. How does calling overloaded constructors work?
C# picks the right constructor automatically at compile time, based on the type and number of arguments you pass. It's hard to mess up — if you do, the compiler will yell at you right away.
Let's try adding a constructor with an int parameter:
public Cat(int age)
{
Name = "Nameless cat";
Color = "Gray";
// Extra code for age
}
Now we can also create a "cat by age":
Cat oldCat = new Cat(5); // This will call Cat(int age)
| Constructor call | Which one gets called? |
|---|---|
|
No parameters |
|
With one string parameter |
|
With two string parameters |
|
With one int parameter |
4. Internal constructor calls: the this keyword
Sometimes, different constructors do similar (or the same) stuff. To avoid copy-pasting code, you can "call one constructor from another". For that, use the this keyword.
public class Cat
{
public string Name;
public string Color;
public Cat() : this("Nameless cat")
{
// This constructor calls Cat(string name)
}
public Cat(string name) : this(name, "Gray")
{
// This constructor calls Cat(string name, string color)
}
public Cat(string name, string color)
{
Name = name;
Color = color;
}
}
So in the end:
- new Cat() calls Cat(string name), which calls Cat(string name, string color).
- All "roads lead to Rome": meaning all the initialization happens in one "main" constructor.
This approach is called constructor chaining.
5. Real-life examples
Let's make a hero class (Hero) for a game, with a name and a level! Here's how we'd write it:
With one constructor:
public class Hero
{
public string Name;
public int Level;
public Hero(string name, int level)
{
Name = name;
Level = level;
}
}
Now let's allow creating it in different ways!
Multiple constructors:
public class Hero
{
public string Name;
public int Level;
// If nothing is specified, let the hero be "Nameless level 1"
public Hero() : this("Nameless", 1)
{
}
// If we only know the name, default level is 1
public Hero(string name) : this(name, 1)
{
}
// The main constructor with two parameters
public Hero(string name, int level)
{
Name = name;
Level = level;
}
}
Now you can create a hero in different ways:
Hero h1 = new Hero(); // "Nameless", 1
Hero h2 = new Hero("Arthur"); // "Arthur", 1
Hero h3 = new Hero("Lora", 10); // "Lora", 10
6. Implementation details: hidden gotchas and nuances
First: the compiler doesn't create a "default" empty constructor if you declare at least one other constructor yourself. So, if you wrote your own constructor with parameters but forgot to add a no-parameter constructor, then this call:
Hero h = new Hero(); // Error if there's no parameterless constructor!
will throw a compile error.
Second: if you call the wrong constructor in the chain, you can break your initialization logic. It's important to keep things consistent: it's best if all the real work happens in the most complete constructor, and the others just pass data there via this(...).
Third: if parameters only differ by type (like Cat(string s) and Cat(object o)), you can accidentally get confusion when calling the constructor with an argument like null. The compiler won't always know which constructor you want.
7. Overloading and field initialization
Often, a class has fields that must be set. Constructor overloading helps you do this conveniently, but you decide what default values make sense for your class.
Example with a check:
public class Book
{
public string Title;
public int Year;
// Default book — "Untitled", year 2000
public Book() : this("Untitled", 2000)
{
}
public Book(string title) : this(title, 2000)
{
}
public Book(string title, int year)
{
Title = title;
Year = year;
}
}
8. Overloading with different parameter sets
You might see classes with dozens of constructors! That's not always a sign of good architecture (sometimes it's better to use "setup" methods or the "builder" pattern), but for user objects, models, DTOs — it's totally fine.
Example: a "Pet" class, where you can specify all the details, or just the basics.
public class Pet
{
public string Name;
public int Age;
public string Type;
public bool IsVaccinated;
// Default constructor
public Pet() : this("NoName", 0, "Cat", false) { }
// Minimum info
public Pet(string name, string type) : this(name, 0, type, false) { }
// Full constructor
public Pet(string name, int age, string type, bool isVaccinated)
{
Name = name;
Age = age;
Type = type;
IsVaccinated = isVaccinated;
}
}
Difference between method overloading and constructor overloading
| Regular method | Constructor (including overloaded) |
|---|---|
| Can be called anytime | Only called when creating an object (new) |
| Can return values of any type | Never returns a value (no type specified) |
| Method name can be anything, usually a verb | Name always matches the class name |
| Can be overloaded by parameters | Also overloaded by parameters |
GO TO FULL VERSION