Constructors and Properties

Object initializers set properties right after construction: new Sensor("coolant") { Unit = "C" }.

Concept

public Sensor() { } Provided automatically only if you declare no constructor

public Vehicle(string vin, string model, int year) Establishes required state

public Vehicle(string vin): this(vin, "Unknown", 0) { } Reuse another constructor; : base(...) calls the base class static Vehicle() {... }

Runs once, automatically, before the type is first used Primary (C# 12) class Logger(string channel) {... }

Parameters in scope for the whole class body public double Limit { get; set; } = 105;

public string Vin { get; } (set in constructor) public double StoredKwh { get; private set; } Init-only (C# 9)

public string Unit { get; init; } — settable only during object initialization public bool IsRedline => Rpm >= 6000; Required (C# 11)

public required string Code { get; init; } — callers must set it

set { if (value < 0) throw...; _x = value; } field keyword (C# 14)

set => field = Math.Clamp(value, 0, 6500); — access the compiler-generated backing field

Object initializers set properties right after construction: new Sensor("coolant") { Unit = "C" }. Property patterns (if (ecu is { Address: 0x7E0 })) match on property values (Module 22).

Why It Matters

Constructors decide whether an object can ever be invalid; properties are the public face of a class. Modern features (required, init, primary constructors) remove boilerplate while keeping objects safe and immutable.

Syntax

public class T
{
    public T(int x) : this(x, 0) { }
    public T(int x, int y) { ... }
    static T() { ... }
    public int A { get; init; }
    public required string B { get; init; }
    public int C { get; set => field = value; }
}

Example

// Constructor chaining, static constructor, primary constructor, init, required, field keyword
var v1 = new Vehicle("EHBAUREV250000001");
var v2 = new Vehicle("EHBAUREV250000002", "Aurora EV", 2025);
Console.WriteLine(v1);
Console.WriteLine(v2);

var sensor = new Sensor("coolant") { Unit = "C" };   // object initializer with init setter
// sensor.Unit = "F";                                 // error: init-only
Console.WriteLine($"{sensor.Name} [{sensor.Unit}] limit {sensor.Limit}");

var ecu = new Ecu { Code = "VCU", Address = 0x7E0 };  // required members must be set
Console.WriteLine($"{ecu.Code} at 0x{ecu.Address:X3}, firmware '{ecu.Firmware}'");

var gauge = new Gauge();
gauge.Rpm = 7200;                                     // clamped by the setter
Console.WriteLine($"gauge rpm = {gauge.Rpm}, redline? {gauge.IsRedline}");

var logger = new Logger("CAN");                       // primary constructor
logger.Log("frame received");

public class Vehicle
{
    public static readonly DateTime RegistryOpened;
    static Vehicle()                                   // static constructor: runs once, before first use
    {
        RegistryOpened = new DateTime(2026, 1, 1);
        Console
…

Expected Result

The static constructor ran exactly once, before the first Vehicle was created. Setting Rpm = 7200 stored 6500 because the setter clamps the value.

[static constructor ran]
EHBAUREV250000001 Unknown ?
EHBAUREV250000002 Aurora EV 2025
coolant [C] limit 105
VCU at 0x7E0, firmware 'unknown'
gauge rpm = 6500, redline? True
[CAN] frame received

Common Mistakes

Forgetting a required member — real compiler error:

Doing heavy work (I/O, network) in constructors — use factory methods or async initialization.

Calling virtual methods from constructors (the derived part is not initialized yet).

Public setters on properties that should never change after creation — use init or get-only.

Throwing from a static constructor: the type becomes unusable (TypeInitializationException) for the rest of the process.

var e = new Ecu { Code = "BMS" };
Console.WriteLine(e.Code);

class Ecu
{
    public required string Code { get; init; }
    public required int Address { get; init; }
}
m14_err.cs(1,13): error CS9035: Required member 'Ecu.Address' must be set in the object initializer or attribute constructor.

Best Practices

Require essential data through constructor parameters or required members.

Prefer immutable objects (init, get-only) for data that should not change.

Keep constructors fast and side-effect free; validate arguments.

Use primary constructors for simple dependency-holding classes; assign to properties or fields when values must be validated.

Interview Relevance

High: "static constructor — when does it run?", "constructor chaining", "init vs set vs readonly", "what are required members?", "primary constructors".

Practice Questions

Add a constructor to Vehicle that takes only a model name and generates a VIN.

Create a Temperature class with Celsius settable and Fahrenheit computed.

Make a CanFilter class with required Id and Mask and an optional Name.

Show the order of execution: base constructor, derived constructor, field initializers.

Quick Quiz

Q1. (True/False) A static constructor can be called explicitly by your code.

Answer: False. The runtime runs it once, automatically, before first use.

Q2. (Multiple choice) Which property can be set only in an object initializer or constructor? A) { get; set; } B) { get; init; } C) { get; } D) { set; }

Answer: B. ({ get; } can only be set in the constructor, not in an initializer.)

Q3. (Predict the output) gauge.Rpm = 7200; with set => field = Math.Clamp(value, 0, 6500); — what does gauge.Rpm return?

Answer: 6500 (verified).

Q4. (Find the bug) new Ecu { Code = "BMS" } fails to compile. Why?

Answer: Address is required and was not set (CS9035, verified).

Coding Challenge

Write a TestCase class with a required Id, an init-only Timeout defaulting to 30 seconds, a computed IsSlow (timeout > 60 s), and a constructor-chained Clone behaviour via with-like copying using a copy constructor.

var tc = new TestCase { Id = "TC-001", Timeout = TimeSpan.FromSeconds(90) };
var copy = new TestCase(tc) { Id = "TC-001-copy" };
Console.WriteLine($"{tc.Id} slow: {tc.IsSlow}; {copy.Id} timeout {copy.Timeout.TotalSeconds}s");

class TestCase
{
    public TestCase() { }
    [System.Diagnostics.CodeAnalysis.SetsRequiredMembers]
    public TestCase(TestCase other) : this()
    {
        Id = other.Id;
        Timeout = other.Timeout;
    }
    public required string Id { get; init; }
    public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(30);
    public bool IsSlow => Timeout > TimeSpan.FromSeconds(60);
}