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, TValue

Common 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;