New Features in .NET 8 (with examples)
.NET 8 is the next major release of the .NET platform. It brings several new features that improve the developer experience and productivity.
Here are some of the major highlights with examples:
Enhanced Nullable Reference Types
Nullable reference types were introduced in C# 8 to help catch null reference exceptions at compile time. .NET 8 improves this feature with:
Better type inference
More usability improvements
Better tooling support
This makes nullable reference types easier to use and more effective.
string name = null; // Compiler warning
string? name = null; // OK
ValueTasks
ValueTasks are a new type for asynchronous programming. They are similar to Tasks but are more lightweight and suitable for short-running asynchronous operations.
ValueTasks can simplify asynchronous code by allowing await
on value types.
ValueTask<int> GetDataAsync()
{
return new ValueTask<int>(5);
}
int result = await GetDataAsync();
Init Only Setters
Init only setters allow properties to be set only during object initialization. After initialization, attempts to set the property will result in a compiler error.
This ensures that certain properties never change after the object is initialized.
public class Person
{
public string Name { get; init; }
}
Person p = new Person { Name = "John"};
p.Name = "Jane"; // Compiler error
Default Interface Methods
Default interface methods allow interfaces to define default implementations for their methods. This makes interfaces more flexible and usable.
interface ILog {
void Log(string msg); // Declaration
void Log(string msg) => Console.WriteLine(msg); // Default implementation
}
Record Types
A new reference type that you can create instead of classes or structs. Records are immutable data containers.
record Student(string Name, int Age);
Student s = new Student("John", 20);
Async disposal
Asynchronous disposal allows disposing resources asynchronously.
public class AsyncDisposable : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await SomeAsyncOperation();
}
}
Usage:
using (var d = new AsyncDisposable())
{
//...
} // DisposeAsync is called automatically
Module Imports
Modules allow importing types from other modules.
module Math
{
public static class Functions
{
public static int Add(int x, int y) => x + y;
}
}
module App
{
import Math;
public void Test()
{
int result = Functions.Add(1, 2);
}
}
Static Local Functions
Static local functions are local to a containing method:
static void Main()
{
int result = DoCalculation(5);
static int DoCalculation(int num)
{
static int Square(int x) => x * x;
return Square(num);
}
}
Enhanced Async Streams
Async streams allow awaiting data from an asynchronous data source:
async IAsyncEnumerable<int> GetDataAsync()
{
await Task.Delay(1000); // Simulate async operation
yield return 1;
yield return 2;
}
await foreach (var data in GetDataAsync())
{
Console.WriteLine(data);
}
Pattern Matching Improvements
New options like with
and relational
patterns:
object o = 1;
if (o is int i) { ... } // Simple pattern
if (o is int i with > 0) { ... } // with pattern
if (o is int i when i < 10) { ... } // relational pattern
Pattern Matching for Nullable Types:
int? value = null;
if (value is null)
{
// Handle null case
}
else if (value is > 0)
{
// Handle positive case
}
Type Pattern Matching:
object obj = new List<string>();
if (obj is List<string> list)
{
// obj is a string list
}
else if (obj is Dictionary<int, string> dict)
{
// obj is a dictionary
}
Overall, .NET 8 brings some great productivity improvements to the .NET ecosystem including the above features.