Comparing .NET 8 and .NET 6

Microsoft’s .NET is a popular cross-platform framework that is used to build various types of applications, from web to desktop and mobile. With the release of .NET 8, many new features have been added that enhance its capabilities.

In this article, we will compare some of the new features in .NET 8 with how they were achieved prior to their release.

Native AOT (Ahead-of-Time) improvements

Native AOT (Ahead-of-Time) is a method of compiling code that allows the code to run without requiring a runtime environment. In the context of .NET, it refers to the ability to compile .NET code into native machine code that can be executed directly on the target machine, without requiring the .NET runtime to be installed. This results in faster startup times and reduced memory usage, as well as the ability to create a self-contained application that doesn’t require a separate runtime installation.

This feature was first introduced in .NET 7, and with .NET 8, we can expect even more improvements.

One of the major updates in .NET 8 is the addition of support for the x64 and Arm64 architectures on macOS. This opens up new opportunities for developers to target a wider range of platforms and devices.

Another major improvement in .NET 8 is the reduction in size of native AOT apps on Linux. With the improvements made in .NET 8, the sizes of native AOT apps on Linux can be up to 50% smaller. For example, the size of a “Hello World” app published with native AOT and including the entire .NET runtime on .NET 7 is 3.76 MB, while the same app on .NET 8 Preview 1 is only 1.84 MB.

New higher-performance types

.NET 8 includes several new types that are designed to improve app performance. These types include FrozenDictionary<TKey,TValue> and FrozenSet<T> in the new System.Collections.Frozen namespace, which are optimized for read operations and don’t allow changes to keys and values after creation. Another new type is System.Buffers.IndexOfAnyValues<T>, which is designed to be passed to methods that look for the first occurrence of any value in the passed collection. Finally, System.Text.CompositeFormat is useful for optimizing format strings that aren’t known at compile time. These new types offer performance improvements over their counterparts in previous versions of .NET, making .NET 8 a promising update for developers looking to build high-performance applications.

Shuffle Collections with Random.Shuffle()

The new Random.Shuffle and RandomNumberGenerator.Shuffle(Span) methods in .NET 8 let you randomize the order of a span. This is a helpful feature, particularly for reducing training bias in machine learning applications. In .NET 6, there was no built-in way to shuffle a collection. However, you could write a custom extension method to shuffle a List. Here's an example of how you might have shuffled a list in .NET 6:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static Random _random = new Random();
public static void Shuffle<T>(List<T> list)
{
    int n = list.Count;
    while (n > 1)
    {
        n--;
        int k = _random.Next(n + 1);
        T value = list[k];
        list[k] = list[n];
        list[n] = value;
    }
}
// ... and you'd call it this way:

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
Shuffle(numbers);

In .NET 8, the shuffle method is built-in and is much simpler to use. Here’s an example of how you could shuffle an array of integers:

1
2
int[] numbers = new int[] { 1, 2, 3, 4, 5 };
Random.Shared.Shuffle(numbers);

Improved Async Streams

Async streams are used to efficiently read and write data in .NET. In .NET 6, async streams have limited capabilities, and we need to use workarounds to make them work properly. However, in .NET 8, async streams have been improved, and we can use them more easily. Here’s an example:

1
2
3
4
5
6
7
8
public async IAsyncEnumerable<int> GetNumbersAsync(int count)
{
    for (int i = 0; i < count; i++)
    {
        await Task.Delay(1000);
        yield return i;
    }
}

In .NET 8, we can use the await foreach statement to read data from async streams:

1
2
3
4
await foreach (int number in GetNumbersAsync(5))
{
    Console.WriteLine(number);
}

The introduction of IAsyncEnumerator and await foreach in .NET 8 greatly simplifies the process of consuming data from an async stream. By providing a more natural and intuitive syntax, it streamlines the code and eliminates the need for boilerplate code. This not only demonstrates the improved efficiency and ease-of-use of .NET 8, but also showcases the language’s continued evolution towards more streamlined and intuitive syntax.

Improved JSON serialization from interfaces

In .NET 8, System.Text.Json now supports serializing properties from interface hierarchies. This means that properties from both the immediately implemented interface and its base interface can be serialized. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface IBase
{
    public int Base { get; set; }
}

public interface IDerived : IBase
{
    public int Derived { get; set; }
}

public class DerivedImplement : IDerived
{
    public int Base { get; set; }
    public int Derived { get; set; }
}

IDerived value = new DerivedImplement { Base = 0, Derived = 1 };
JsonSerializer.Serialize(value);

The output will be:

{"Base":0,"Derived":1}


This shows how easy it is to serialize properties from interface hierarchies with System.Text.Json in .NET 8.

Conclusion

.NET 8 brings lots of exciting new features and enhancements that can help developers build high-performance, modern applications more easily.

From support for interface hierarchies to performance-focused types, improved async streams and JSON serialization improvements, .NET 8 promises to streamline and optimize development workflows. Additionally, the option to publish apps as native AOT will offer more flexibility and control over application deployment.

As we eagerly await the release of .NET 8, we can look forward to exploring these new features and discovering new ways to improve our development workflows and build more powerful applications.