Constructors and Object Initialization

Master C# constructors including parameterized, static, copy constructors, and modern object initialization patterns

C#
OOP
Constructors
Initialization
18 min read

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() or base() is called before the constructor body executes.

Common Interview Questions

  1. 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.
  2. What is constructor chaining?

    • Calling one constructor from another using this() for same class or base() for parent class. Executes the chained constructor first, then the current constructor body.
  3. 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.
  4. Can you have multiple static constructors?

    • No, only one static constructor per class, and it cannot be overloaded (no parameters allowed).
  5. 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.
  6. 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.
  7. 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.