Chapter 15 Csharp 8 And Beyond
2 min read- Chapter 15 — C# 8 and beyond
- 15.1 Nullable reference types (NRT)
- 15.1.1 What problem do they solve?
- 15.1.2 Changing meaning for reference types
- 15.1.3 Enter NRT (compiler-assisted contracts)
- 15.1.4 Compile time vs runtime
- 15.1.5 The “damn it” / bang operator (`!`)
- 15.1.6 Migration experience
- 15.2 Switch expressions
- 15.3 Recursive pattern matching
- 15.3.1 Property patterns
- 15.3.2 Deconstruction patterns
- 15.3.3 Omitting types
- 15.4 Indexes and ranges
- 15.5 More async integration
- 15.5.1 Async disposal (`await using`)
- 15.5.2 Async iteration (`await foreach`)
- 15.5.3 Async iterators
- 15.6 Features not yet in preview (book context)
- 15.7 Getting involved
Chapter 15 — C# 8 and beyond
Purpose of this chapter: understand the C# 8 feature set (and nearby future items) that meaningfully change how we write safe, expressive code: nullable reference types, switch expressions and richer patterns, ranges/indexes, and deeper async integration.
15.1 Nullable reference types (NRT)
Nullable reference types are about making nullability explicit in the type system so the compiler can warn about null mistakes.
15.1.1 What problem do they solve?
They reduce:
- NullReferenceExceptions in production.
- “Maybe null” ambiguity in APIs.
15.1.2 Changing meaning for reference types
With NRT enabled:
stringtypically means “non-null string”.string?means “may be null”.
15.1.3 Enter NRT (compiler-assisted contracts)
The compiler tracks null-state flow:
- It warns when you might dereference null.
- It warns when you assign a maybe-null to a non-nullable reference.
15.1.4 Compile time vs runtime
Important: NRT is primarily a compile-time feature.
- It doesn’t change runtime behavior of references (null still exists).
15.1.5 The “damn it” / bang operator (!)
x! means “I assert this isn’t null”.
Guideline:
- Use sparingly; treat it as a localized escape hatch when you can prove invariants but the compiler can’t.
15.1.6 Migration experience
Practical approach:
- Enable NRT in a controlled way, fix warnings in layers, add annotations to public APIs, and keep warning counts trending down.
15.2 Switch expressions
Switch expressions make mapping logic concise and expression-oriented:
- Great for “value in → value out” mappings.
Guideline:
- Use when it improves clarity; don’t cram complex logic into a single expression if a statement-based switch is clearer.
15.3 Recursive pattern matching
Patterns can match deeper structure, not just top-level types.
15.3.1 Property patterns
Match based on property values (shape + constraints).
15.3.2 Deconstruction patterns
Match using Deconstruct shape.
15.3.3 Omitting types
Patterns can often infer types; use when it stays readable.
15.4 Indexes and ranges
Indexes/ranges make slicing more expressive:
^1means “from the end”.a[1..^1]means a slice/range (excluding endpoints based on indices).
Guideline:
- Great for parsing and data processing, but keep boundaries clear and test off-by-one behavior.
15.5 More async integration
15.5.1 Async disposal (await using)
Use for resources requiring async cleanup:
- Important for I/O resources where disposal can be asynchronous.
15.5.2 Async iteration (await foreach)
Consume IAsyncEnumerable<T> streams without buffering everything in memory.
15.5.3 Async iterators
Produce async streams with async + yield return semantics (conceptually).
Interview angle:
- These matter in high-throughput services for streaming data pipelines and backpressure-aware designs.
15.6 Features not yet in preview (book context)
The book discusses items that became real later (or evolved), e.g.:
- Default interface methods
- Records
Takeaway:
- Know the “direction of travel”: safer types, richer patterns, and better async ergonomics.
15.7 Getting involved
Practical suggestion:
- Track language proposals and adopt features deliberately; avoid churn without a payoff.