← All topics
🧰 Core Tech Refresher

Modern C# (8–13)

Records, pattern matching, nullable reference types, primary constructors, required members.

The client's stack is C# on .NET 8/9/10, so expect them to notice if you're still writing C# like it's 2015. Know these by name and be able to say why you'd reach for them, not just what they do.

Records — immutable-by-default reference (or record struct for value) types with built-in value equality and with-expressions for non-destructive mutation. Ideal for DTOs, CQRS messages, and DDD value objects.

public record OrderPlaced(Guid OrderId, string CustomerId, decimal Total);

var original = new OrderPlaced(Guid.NewGuid(), "cust-1", 99.90m);
var corrected = original with { Total = 89.90m }; // new instance, original untouched

Pattern matchingswitch expressions, property patterns, and relational patterns let you express branching logic declaratively instead of nested ifs (this is directly relevant to the Strategy pattern topic — pattern matching is the "quick and dirty" alternative interviewers expect you to know and know the limits of).

double Discount(Customer c) => c switch
{
    { Type: CustomerType.Premium, YearsActive: > 5 } => 0.25,
    { Type: CustomerType.Premium } => 0.20,
    { Type: CustomerType.Regular } => 0.10,
    _ => 0
};

Nullable reference types (enable) — the compiler tracks nullability as part of the type system and warns on unguarded dereferences. Say this explicitly if asked about null safety: it's a compile-time analysis, not a runtime guarantee — ! (null-forgiving) bypasses it.

Primary constructors (C# 12) — constructor parameters usable directly in the class body without manually declaring fields, now valid on regular classes, not just records.

public class DiscountService(IDiscountStrategyFactory factory)
{
    public double Calculate(string customerType, double price) =>
        factory.GetStrategy(customerType).Calculate(price);
}

Required members (C# 11) — required properties force callers to set them via object initializer, giving you DTO immutability-ish guarantees without a big constructor.

Flashcards (5)

What does a C# record give you that a class doesn't, by default?
tap to reveal answer
Value-based equality (two records with the same property values are ==), a compiler-generated ToString(), and non-destructive mutation via with expressions.
What's the difference between record and record struct?
tap to reveal answer
record is a reference type (heap-allocated, nullable); record struct is a value type (stack-allocated/inline, no null by default). Use record struct for small, frequently-copied value objects.
What does <Nullable>enable</Nullable> actually enforce?
tap to reveal answer
Compile-time static analysis only — the compiler warns when you dereference a possibly-null reference without a check. It does NOT add runtime null checks; ! suppresses the warning without adding safety.
When would you prefer a switch expression with property patterns over an if/else chain?
tap to reveal answer
When branching on the shape/state of an object rather than a single flag — it reads as a decision table, is exhaustive-checked by the compiler, and is a natural stepping stone toward Strategy/polymorphism.
What do primary constructors let you do in C# 12 that's new compared to records-only primary constructors?
tap to reveal answer
Use the concise constructor-parameter syntax on ordinary classes and structs, not just records — useful for lightweight constructor-injection classes like services.