New Features in .NET 8 (with examples)

Jun 27, 2023ยท

3 min read

.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.