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.