Modern C# and the Advanced Type System
Most of these appeared in earlier modules; this module puts them together.
Concept
Modern Language Constructs (C# 9–14) init accessors public string Code { get; init; } Set only during object initialization required members
public required int Address { get; init; } The compiler forces callers to set them record + with ecu with { Firmware = "2.4.1" }
Immutable data with value equality and non-destructive mutation namespace SampleApp.Diag; One less indentation level global using global using System.Text;
Import once for the whole project (plus implicit usings) No Main boilerplate Raw string literals """{ "a": 1 }""" No escaping of quotes or backslashes Interpolated raw strings $$"""{ "x": {{value}} }"""
The number of $ sets how many braces start an interpolation [_, 0x10, var s] Match sequences by shape { Speed: > 120 } Match by member values
One syntax for arrays, lists, spans; .. spreads class Logger(string prefix)
Constructor parameters in scope for the whole type Extended nameof
nameof of parameters inside attributes x switch {... } Expression-based matching (Module 22)
Most of these appeared in earlier modules; this module puts them together.
Advanced Type System Type inference: var, generic method inference, natural types for lambdas (var add = (int x, int y) => x + y; is a Func<int,int,int>), target-typed new().
Generic constraints: where T : class / struct / notnull / unmanaged / new() / BaseType / IInterface and static abstract interface members — the basis of generic math (INumber<T>), which lets one Average<T> work for int, double and decimal.
Variance in generic interfaces and delegates: covariance (out T, e.g. IEnumerable<string> → IEnumerable<object>) and contravariance (in T, e.g. IComparer<object> → IComparer<string>). Works only for reference types and interfaces/delegates, not classes.
Nullable annotations: string?, [NotNullWhen(true)], [MaybeNull] refine flow analysis (Module 21).
Function pointers: delegate*<int, int, int> — raw, allocation-free pointers to static methods, used in interop (unsafe).
dynamic: typed as object with late binding (Module 37).
Type metadata and open generics: typeof(Dictionary<,>) is an *open* generic type; MakeGenericType closes it at run time.
Why It Matters
Modern syntax makes data-heavy engineering code (frames, signals, configs, DTOs) shorter and safer. Generic math and variance let libraries be both reusable and type-safe; interviewers probe variance because it is often misunderstood.
Syntax
public required string Code { get; init; }
var copy = ecu with { Firmware = "2.4.1" };
string json = $$"""{ "ecu": "{{code}}" }""";
static T Average<T>(T[] v) where T : INumber<T> { ... }
IEnumerable<object> objs = new List<string>(); // covariance
delegate*<int, int, int> fp = &Mul; // function pointer (unsafe)Example
40.1 Modern features and the type system together Function pointers need unsafe code, enabled by #:property AllowUnsafeBlocks=true (Module 42).
#:property AllowUnsafeBlocks=true
using System.Numerics;
// init / required / records / with
var ecu = new Ecu { Code = "VCU", Address = 0x7E0 };
var flashed = ecu with { Firmware = "2.4.1" };
Console.WriteLine($"{ecu} -> {flashed}");
// Raw string literals and interpolated raw strings
string code = ecu.Code;
string json = $$"""
{ "ecu": "{{code}}", "address": {{ecu.Address}} }
""";
Console.WriteLine(json);
// Collection expressions and spread
int[] a = [1, 2];
List<int> b = [.. a, 3, .. new[] { 4, 5 }];
Console.WriteLine($"collection expression: [{string.Join(",", b)}]");
// List + property patterns and modern switch expressions
Console.WriteLine(Classify([0x02, 0x10, 0x03]));
Console.WriteLine(Classify([0x03, 0x7F, 0x22, 0x31]));
// Primary constructors
var logger = new PrefixLogger("DIAG");
logger.Log("session opened");
// Extended nameof (can reference parameters in attributes) and CallerArgumentExpression
Guard.NotNegative(-3 + 1);
// Type inference and natural lambda types
var add = (int x, int y) => x + y;
Console.WriteLine($"natural lambda type: {add.GetType().Name}");
// Generic math with static abstract interface members
Console.WriteLine($"Average<int> {A
…Expected Result
Average<double> of 0.5 and 1.5 is exactly 1, and CallerArgumentExpression captured the caller's source text -3 + 1, not just the value.
Ecu { Code = VCU, Address = 2016, Firmware = unflashed } -> Ecu { Code = VCU, Address = 2016, Firmware = 2.4.1 }
{ "ecu": "VCU", "address": 2016 }
collection expression: [1,2,3,4,5]
DiagnosticSessionControl session 3
NRC 0x31 for service 0x22
[DIAG] session opened
Guard: '-3 + 1' evaluated to -2, which is negative
natural lambda type: Func`3
Average<int> 5, Average<double> 1, Average<decimal> 2.2
covariant list count 1, contravariant comparer result -1
function pointer Mul(6, 7) = 42
Dictionary`2 has 2 type parameters: TKey, TValueCommon Mistakes
Expecting variance on classes (List<string> → List<object> does not compile) — only interfaces and delegates.
Assuming with makes a deep copy — reference-type members are shared.
Using $""" when the text contains braces — use $$""" so single braces stay literal.
Over-using dynamic or var where an explicit type documents intent.
Forgetting that required members must be set even when a constructor exists (unless it has [SetsRequiredMembers]).
Best Practices
Use records with required/init for immutable data, classes for behaviour and identity.
Prefer generic constraints and generic math to duplicating code per numeric type.
Accept the most general interface (IEnumerable<T>, IReadOnlyList<T>) to benefit from covariance.
Enable nullable reference types and treat warnings as errors in new projects.
Interview Relevance
High: "covariance vs contravariance with examples?", "record vs class?", "what does required do?", "what are static abstract interface members?", "init vs readonly?".
Practice Questions
Write Max<T>(T a, T b) where T : IComparisonOperators<T, T, bool>.
Show a compile error when assigning List<string> to List<object> and fix it with IReadOnlyList<object>.
Create a record CanSignal with required members and a with copy that changes the scale.
Use a list pattern to detect ISO-TP first frames ([0x10 | ..., ...]).
Quick Quiz
Q1. (Multiple choice) Which interface is covariant? A) IList<T> B) IEnumerable<out T> C) IComparer<in T> D) ICollection<T> Answer: B.
Q2. (Predict the output) var add = (int x, int y) => x + y; Console.WriteLine(add.GetType().Name); Answer: `Func3`` (verified).
Q3. (True/False) $$"""{ "a": {{x}} }""" outputs the value of x inside literal braces.
Answer: True (verified: { "ecu": "VCU", "address": 2016 }).
Q4. (Find the bug) var e = new Ecu { Code = "VCU" }; where Address is required.
Answer: Compile error CS9035 — *Required member 'Ecu.Address' must be set in the object initializer* (verified).
Coding Challenge
Write a generic Clamp<T>(T value, T min, T max) using generic math and call it with int, double and decimal.
using System.Numerics;
Console.WriteLine(Clamp(130, 0, 120));
Console.WriteLine(Clamp(-4.5, 0.0, 5.0));
Console.WriteLine(Clamp(12.6m, 11.5m, 14.8m));
static T Clamp<T>(T value, T min, T max) where T : INumber<T> =>
value < min ? min : value > max ? max : value;