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 receivedCommon 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);
}