C# 8.0 shipped with .NET Core 3.0 in September 2019. The feature set is more impactful than any C# release since C# 5.0 introduced async/await.

Nullable reference types

Nullable reference types (NRT) make the null-safety of reference types explicit in the type system. With NRT enabled, string is non-nullable (cannot be null without a compiler warning); string? is nullable. The compiler warns when a nullable value is used without a null check and when a non-nullable variable is assigned a potentially null value. Enabling NRT on existing codebases surfaces hundreds of implicit nullability assumptions that are potential NullReferenceExceptions.

Switch expressions

Switch expressions replace verbose switch statements with concise expression syntax. The arms of a switch expression use pattern matching: type patterns, property patterns, tuple patterns, and positional patterns. The switch expression is an expression (has a value) rather than a statement, enabling its use in property initialisers, ternary expressions, and LINQ. The pattern matching capability makes discriminated union-style code natural in C#.

Async streams

Async streams (IAsyncEnumerable) allow async iteration: a method that returns IAsyncEnumerable yields values asynchronously, and the consumer uses await foreach. The pattern is natural for: streaming database queries (yield each row as it arrives rather than buffering all rows), consuming message queues one message at a time, and reading paginated API responses without buffering all pages. IAsyncEnumerable bridges the gap between async/await and LINQ's IEnumerable.

Ranges and indices

The index-from-end operator (^) and range operator (..) enable concise slice syntax for arrays and spans: array[^1] is the last element, array[1..^1] is all elements except first and last. The System.Range and System.Index types make this work with any indexable type that implements the required pattern. Combined with Span, ranges provide efficient slice operations without array allocation.