Overview
Understanding constructor patterns is essential for proper object initialization, dependency injection, and creating well-designed APIs. C# offers multiple approaches for different scenarios.
Core Concepts
Constructor Overloading
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
public bool InStock { get; set; }
// Multiple constructors for flexibility
public Product()
{
Name = "Unnamed";
Price = 0;
Category = "General";
InStock = false;
}
public Product(string name) : this() // Call default first
{
Name = name;
}
public Product(string name, decimal price) : this(name)
{
Price = price;
}
public Product(string name, decimal price, string category, bool inStock = true)
{
Name = name;
Price = price;
Category = category;
InStock = inStock;
}
}
// Usage - multiple ways to create
var p1 = new Product();
var p2 = new Product("Widget");
var p3 = new Product("Gadget", 29.99m);
var p4 = new Product("Tool", 49.99m, "Hardware");
var p5 = new Product("Part", 9.99m, "Supplies", false);
Constructor Chaining
public class Rectangle
{
public double Width { get; }
public double Height { get; }
public string Color { get; }
// Primary constructor with all parameters
public Rectangle(double width, double height, string color)
{
Width = width > 0 ? width : throw new ArgumentException("Width must be positive");
Height = height > 0 ? height : throw new ArgumentException("Height must be positive");
Color = color ?? "Black";
}
// Chain to primary - creates square
public Rectangle(double size) : this(size, size, "Black") { }
// Chain to primary - with default color
public Rectangle(double width, double height) : this(width, height, "Black") { }
}
Static Constructors
public class Configuration
{
// Static readonly fields initialized by static constructor
public static readonly string Environment;
public static readonly string ConnectionString;
public static readonly DateTime StartupTime;
// Static constructor - runs once before first use
static Configuration()
{
StartupTime = DateTime.UtcNow;
Environment = System.Environment.GetEnvironmentVariable("ENVIRONMENT") ?? "Development";
// Load configuration based on environment
ConnectionString = Environment switch
{
"Production" => "Server=prod;Database=app;",
"Staging" => "Server=staging;Database=app;",
_ => "Server=localhost;Database=app_dev;"
};
}
// Instance constructor
public Configuration()
{
// Instance initialization
}
}
// Static constructor runs automatically before first access
Console.WriteLine(Configuration.Environment); // Triggers static constructor
Primary Constructors (C# 12)
// Traditional class
public class PersonTraditional
{
private readonly string _name;
private readonly int _age;
public PersonTraditional(string name, int age)
{
_name = name;
_age = age;
}
public string Name => _name;
public int Age => _age;
}
// With primary constructor (C# 12)
public class Person(string name, int age)
{
public string Name => name;
public int Age => age;
// Can still have methods using the parameters
public string GetGreeting() => $"Hello, I'm {name}, {age} years old";
}
// Primary constructor with validation
public class Order(int id, string customerName)
{
public int Id { get; } = id > 0 ? id : throw new ArgumentException("Invalid ID");
public string CustomerName { get; } = customerName ?? throw new ArgumentNullException(nameof(customerName));
}
// Inheritance with primary constructors
public class Employee(string name, string department) : Person(name, 0)
{
public string Department => department;
}
Object and Collection Initializers
public class Employee
{
public string Name { get; set; }
public string Department { get; set; }
public List<string> Skills { get; set; } = new();
public Address Address { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
}
// Object initializer
var employee = new Employee
{
Name = "Alice",
Department = "Engineering",
Address = new Address // Nested initializer
{
Street = "123 Main St",
City = "Seattle"
},
Skills = { "C#", "SQL", "Azure" } // Collection initializer
};
// Collection initializer
var employees = new List<Employee>
{
new() { Name = "Alice", Department = "Engineering" },
new() { Name = "Bob", Department = "Sales" },
new() { Name = "Charlie", Department = "Engineering" }
};
// Dictionary initializer
var lookup = new Dictionary<string, int>
{
["one"] = 1,
["two"] = 2,
["three"] = 3
};
Required Members and init-only
public class User
{
// Required - must be set during initialization
public required string Username { get; init; }
public required string Email { get; init; }
// Optional with default
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
public bool IsActive { get; init; } = true;
}
// Must provide required members
var user = new User
{
Username = "alice",
Email = "alice@example.com"
};
// Constructor can satisfy required members
public class Order
{
public required string OrderId { get; init; }
public required int CustomerId { get; init; }
[SetsRequiredMembers]
public Order(string orderId, int customerId)
{
OrderId = orderId;
CustomerId = customerId;
}
public Order() { }
}
// Both work
var order1 = new Order("ORD001", 123);
var order2 = new Order { OrderId = "ORD002", CustomerId = 456 };
Interview Tips
Tip 1: Know that if you define any constructor, the default parameterless constructor is no longer auto-generated - you must explicitly define it if needed.
Tip 2: Static constructors cannot have access modifiers or parameters and run exactly once per type.
Tip 3: Constructor chaining with
this()orbase()is called before the constructor body executes.
Common Interview Questions
-
What happens if you don't define a constructor?
- Compiler generates a default parameterless constructor. Once you define any constructor, the default is no longer generated automatically.
-
What is constructor chaining?
- Calling one constructor from another using
this()for same class orbase()for parent class. Executes the chained constructor first, then the current constructor body.
- Calling one constructor from another using
-
When does a static constructor run?
- Runs once, automatically, before the first instance is created OR any static members are accessed. Cannot be called directly. Has no parameters.
-
Can you have multiple static constructors?
- No, only one static constructor per class, and it cannot be overloaded (no parameters allowed).
-
What's the difference between a constructor and an object initializer?
- Constructor: Method that initializes object, can have logic and validation. Object initializer: Syntactic sugar that sets public properties after constructor runs. Constructor executes first, then initializer.
-
What is a private constructor used for?
- Prevent instantiation (singleton pattern, static utility classes), force use of factory methods, prevent inheritance. Common in Singleton pattern.
-
How do you call a parent class constructor?
- Use
base()in the constructor declaration:public Derived(int x) : base(x) { }. Called before the derived constructor body.
- Use