C# Constructor: The Hidden Hero of Object Creation
Constructor: What Happens When an Object is Born?
In the previous post, we learned about Class and Object concepts. We created objects and assigned values to their properties. But every time we had to do this:
Car car1 = new Car();
car1.Brand = "Toyota";
car1.Model = "Corolla";
car1.Year = 2024;
car1.Color = "White";4 lines of code, just to create one car. What if we need to create 100 cars? What if we forget to assign some properties?
This is exactly what Constructor solves.
What is a Constructor?
A Constructor is a special method that is automatically called when an object is created. Its name must be the same as the class name and it has no return type.
public class Car
{
public string Brand { get; set; }
public string Model { get; set; }
public int Year { get; set; }
public string Color { get; set; }
// Constructor
public Car(string brand, string model, int year, string color)
{
Brand = brand;
Model = model;
Year = year;
Color = color;
Console.WriteLine($"🚗 New {brand} {model} manufactured!");
}
}
// Usage - Single line!
Car car1 = new Car("Toyota", "Corolla", 2024, "White");See that? 1 line instead of 4. And the best part: It's now impossible to skip any property because the constructor requires them.
Default Constructor: The Silent Hero
If you don't write any constructor, C# secretly creates one for you. This is called the default constructor.
public class Box
{
public int Width { get; set; }
public int Height { get; set; }
}
// C# creates this behind the scenes:
// public Box() { }
Box box1 = new Box(); // This works
box1.Width = 10;
box1.Height = 20;But beware! If you write your own constructor, the default constructor disappears:
public class Box
{
public int Width { get; set; }
public int Height { get; set; }
public Box(int width, int height)
{
Width = width;
Height = height;
}
}
Box box1 = new Box(); // ❌ ERROR! No parameterless constructor
Box box2 = new Box(10, 20); // ✅ WorksConstructor Overloading: Offering Options
Sometimes you want to offer different options to users. "You can provide just the name, or provide everything." This is called constructor overloading.
public class Player
{
public string Name { get; set; }
public int Level { get; set; }
public int Health { get; set; }
public string Class { get; set; }
// Constructor 1: Name only
public Player(string name)
{
Name = name;
Level = 1; // Default values
Health = 100;
Class = "Warrior";
}
// Constructor 2: Name and class
public Player(string name, string playerClass)
{
Name = name;
Class = playerClass;
Level = 1;
Health = 100;
}
// Constructor 3: Fully equipped
public Player(string name, string playerClass, int level, int health)
{
Name = name;
Class = playerClass;
Level = level;
Health = health;
}
public void ShowInfo()
{
Console.WriteLine($"⚔️ {Name} | Class: {Class} | Level: {Level} | Health: {Health}");
}
}Now users can create objects however they want:
Player player1 = new Player("John");
Player player2 = new Player("Emma", "Mage");
Player player3 = new Player("Mike", "Archer", 50, 500);
player1.ShowInfo(); // ⚔️ John | Class: Warrior | Level: 1 | Health: 100
player2.ShowInfo(); // ⚔️ Emma | Class: Mage | Level: 1 | Health: 100
player3.ShowInfo(); // ⚔️ Mike | Class: Archer | Level: 50 | Health: 500Constructor Chaining with this()
Did you notice the code repetition between constructors? We're doing the same assignments every time. With this(), we can call one constructor from another:
public class Player
{
public string Name { get; set; }
public int Level { get; set; }
public int Health { get; set; }
public string Class { get; set; }
// Main constructor - All parameters
public Player(string name, string playerClass, int level, int health)
{
Name = name;
Class = playerClass;
Level = level;
Health = health;
Console.WriteLine($"✨ {name} joined the game!");
}
// Name only - Call main constructor
public Player(string name) : this(name, "Warrior", 1, 100)
{
// Extra code can be added here
}
// Name and class - Call main constructor
public Player(string name, string playerClass) : this(name, playerClass, 1, 100)
{
}
}The : this(...) syntax means "run that constructor first, then run this constructor's body."
Optional Parameters: The Modern Approach
One of C#'s nice features: Optional parameters. You can achieve the same flexibility with a single constructor instead of overloading.
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public int Stock { get; set; }
public string Category { get; set; }
public bool IsActive { get; set; }
public Product(
string name,
decimal price,
int stock = 0, // Default: 0
string category = "General", // Default: "General"
bool isActive = true) // Default: true
{
Name = name;
Price = price;
Stock = stock;
Category = category;
IsActive = isActive;
}
}
// Different usage patterns
Product product1 = new Product("Laptop", 1500);
Product product2 = new Product("Mouse", 50, 100);
Product product3 = new Product("Keyboard", 80, 50, "Electronics");
Product product4 = new Product("Monitor", 800, category: "Electronics"); // Named parameterThanks to named parameters, you can skip middle parameters. Very handy!
Real-World Example: E-Commerce Order System
Let's combine everything we've learned. Think about creating orders in a real e-commerce system:
public class Order
{
public string OrderNumber { get; private set; }
public string CustomerName { get; set; }
public List<string> Products { get; set; }
public decimal TotalAmount { get; set; }
public DateTime CreatedDate { get; private set; }
public string Status { get; private set; }
// Private constructor - Cannot be created directly from outside
private Order()
{
OrderNumber = GenerateOrderNumber();
CreatedDate = DateTime.Now;
Status = "Pending";
Products = new List<string>();
}
// Public constructor - Standard order
public Order(string customerName) : this()
{
CustomerName = customerName;
}
// Fully equipped order
public Order(string customerName, List<string> products, decimal totalAmount) : this()
{
CustomerName = customerName;
Products = products;
TotalAmount = totalAmount;
}
private string GenerateOrderNumber()
{
return $"ORD-{DateTime.Now:yyyyMMdd}-{new Random().Next(1000, 9999)}";
}
public void AddProduct(string productName, decimal price)
{
Products.Add(productName);
TotalAmount += price;
Console.WriteLine($"✅ '{productName}' added to cart. Total: ${TotalAmount:F2}");
}
public void ShowOrderSummary()
{
Console.WriteLine("\n" + new string('=', 40));
Console.WriteLine($"📦 ORDER DETAILS");
Console.WriteLine(new string('=', 40));
Console.WriteLine($"Order No: {OrderNumber}");
Console.WriteLine($"Customer: {CustomerName}");
Console.WriteLine($"Date: {CreatedDate:MM/dd/yyyy HH:mm}");
Console.WriteLine($"Status: {Status}");
Console.WriteLine("\nProducts:");
foreach (var product in Products)
{
Console.WriteLine($" • {product}");
}
Console.WriteLine($"\n💰 TOTAL: ${TotalAmount:F2}");
Console.WriteLine(new string('=', 40));
}
}Usage:
// Start empty order
Order order1 = new Order("John Smith");
order1.AddProduct("iPhone 15", 999);
order1.AddProduct("AirPods Pro", 249);
order1.AddProduct("MagSafe Charger", 39);
order1.ShowOrderSummary();
// Order with ready product list
var products = new List<string> { "MacBook Pro", "Magic Mouse", "USB-C Hub" };
Order order2 = new Order("Emma Johnson", products, 2500);
order2.ShowOrderSummary();Output:
✅ 'iPhone 15' added to cart. Total: $999.00
✅ 'AirPods Pro' added to cart. Total: $1248.00
✅ 'MagSafe Charger' added to cart. Total: $1287.00
========================================
📦 ORDER DETAILS
========================================
Order No: ORD-20260203-4721
Customer: John Smith
Date: 02/03/2026 14:30
Status: Pending
Products:
• iPhone 15
• AirPods Pro
• MagSafe Charger
💰 TOTAL: $1287.00
========================================Static Constructor: Runs When Class Loads
Sometimes you want to do something when a class is first used. Static constructor is perfect for this:
public class Database
{
public static string ConnectionString { get; private set; }
public static bool IsConnected { get; private set; }
// Static constructor - Runs once when class is first used
static Database()
{
Console.WriteLine("🔌 Establishing database connection...");
ConnectionString = "Server=localhost;Database=MyApp;";
IsConnected = true;
Console.WriteLine("✅ Connection ready!");
}
public static void ExecuteQuery(string sql)
{
if (IsConnected)
{
Console.WriteLine($"📊 Executing query: {sql}");
}
}
}
// Static constructor runs on first use
Database.ExecuteQuery("SELECT * FROM Users");
Database.ExecuteQuery("SELECT * FROM Products");
// Output:
// 🔌 Establishing database connection...
// ✅ Connection ready!
// 📊 Executing query: SELECT * FROM Users
// 📊 Executing query: SELECT * FROM ProductsStatic constructor:
- Cannot take parameters
- Has no access modifier
- Runs only once
- We can't control when it runs (CLR decides)
Validation in Constructor: Robustness Matters
Constructor is a perfect place to validate incoming data when an object is created:
public class Email
{
public string Address { get; private set; }
public string Domain { get; private set; }
public Email(string address)
{
if (string.IsNullOrWhiteSpace(address))
throw new ArgumentException("Email address cannot be empty!");
if (!address.Contains("@"))
throw new ArgumentException("Invalid email format!");
if (address.Length < 5)
throw new ArgumentException("Email address is too short!");
Address = address.ToLower().Trim();
Domain = Address.Split('@')[1];
Console.WriteLine($"✅ Email created: {Address}");
}
}
try
{
Email email1 = new Email("[email protected]"); // ✅ Works
Email email2 = new Email("invalid-email"); // ❌ Throws error
}
catch (ArgumentException ex)
{
Console.WriteLine($"❌ Error: {ex.Message}");
}Summary: Constructor Types
| Type | Description | Example |
|---|---|---|
| Default | Parameterless, C# auto-creates | public Class() {} |
| Parameterized | Takes values | public Class(int x) {} |
| Overloaded | Multiple constructors | Different parameter combinations |
| Chained | Calls another constructor | : this(...) |
| Static | Runs when class loads | static Class() {} |
| Private | Not accessible from outside | For Factory pattern |
Next Step: Access Modifiers
With constructors, we can create our objects more safely and consistently.
In the next post, we'll cover Access Modifiers. What do public, private, protected mean? Why is making everything public a bad idea? What are Getters and Setters for?
Keep writing clean code! 🚀
This is the third post of the OOP with C# series. In the previous post, we covered Class and Object concepts, and in the next post, we'll explore Access Modifiers.