✨ Shield now has support for Avalonia UI

Discover the New Features of .NET 8: What You Need to Know

Mar 14, 2023 | .NET

Can you believe it? It feels like just yesterday that we were geeking out over .NET 7, and here we are already talking about .NET 8! Time flies when you’re coding up a storm and this first preview and the .NET 8 Preview 2 are already released.

But fear not, Microsoft is hard at work bringing us the latest and greatest features that we’ll be exploring in this article.

So sit back, relax, and let’s dive into all the exciting new updates that .NET 8 has in store for us.

dotnet publish and dotnet pack

Microsoft just released an awesome new feature for the dotnet publish and dotnet pack commands that makes it even easier to produce production-ready code.

Before this update, these commands produced Debug assets by default, which could be a bit of a hassle if you wanted to produce production-ready code. But now, with the new update, dotnet publish and dotnet pack produce Release assets by default, which means you can easily produce production-ready code without any extra steps.

But don’t worry, if you still need to produce Debug assets for any reason, it’s still possible to do so by setting the in false the PublishRelease property.

How dotnet publish and dotnet pack works?

First, let’s create a new console application using the dotnet new console command. Then, let’s build the project using dotnet build and take a look at the output. In this case, the output will be in Debug mode, since that’s the default behavior of dotnet build.

Next, let’s run the dotnet publish command to produce production-ready code. With the new update, this command will now produce Release assets by default, which is exactly what we want for production code. We can see the Release assets in the /app/bin/Release/net8.0 directory.

Finally, let’s say we need to produce Debug assets for some reason. We can do that by running the dotnet publish command again, but this time we’ll set the PublishRelease property to false. This will produce Debug assets instead of Release assets, which we can see in the /app/bin/Debug/net8.0 directory.

And that’s it! With this new feature, it’s now easier than ever to produce production-ready code using the dotnet publish and dotnet pack commands.

πŸ“šCheck: dotnet publish and dotnet pack

Improvements in System.Text.Json serialization

In this .NET 8 preview 1, System.Text.Json is a built-in .NET library that provides JSON serialization and deserialization functionality. It allows developers to convert .NET objects to JSON data and vice versa.

Now, System.Text.Json serialization and deserialization functionality has been improved in various ways for .NET 8.

Another series of improvements included in this preview are in the source generator when used with ASP.NET Core in Native AOT apps, making it more reliable and faster.

Additionally, the source generator will support in .NET 8 serializing types with required and init properties, which were already supported in reflection-based serialization.

Moreover, customization of serialization for members that aren’t present in the JSON payload is now possible.

Lastly, properties from interface hierarchies can now be serialized, including those from both the immediately implemented interface and its base interface. Let’s check this example:

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

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; }
}

Now, the JsonNamingPolicy has been expanded to include naming policies for snake_case (with an underscore) and kebab-case (with a hyphen) property name conversions. These new policies can be utilized in the same way as the JsonNamingPolicy.CamelCase policy.

var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower };
JsonSerializer.Serialize(new { PropertyName = "value" }, options); // { "property_name" : "value" }

The JsonSerializerOptions.MakeReadOnly() method gives you explicit control over when a JsonSerializerOptions instance is frozen. You can also check whether it’s read-only with the IsReadOnly property.

πŸ“šCheck: System.Text.Json serialization

GetItems<T>()

The GetItems<T>() method is a new feature in .NET 8 that allows you to randomly select a specific number of items from a given set of elements.

This can be useful in games, simulations, and other applications where randomness is desired. The method is available in both System.Random and System.Security.Cryptography.RandomNumberGenerator.

In this example, we have an array of City objects, and we use the GetItems<T>() method to randomly select 3 cities from the array:

private static ReadOnlySpan<City> s_allCities = new[]
{
    new City("New York", "USA"),
    new City("London", "UK"),
    new City("Paris", "France"),
    new City("Tokyo", "Japan"),
    new City("Sydney", "Australia"),
    
};

...

City[] selectedCities = Random.Shared.GetItems(s_allCities, 3);
foreach (City city in selectedCities)
{
    Console.WriteLine(city.Name + ", " + city.Country);
}
// Output:
// Paris, France
// Tokyo, Japan
// Sydney, Australia

We then loop through the selected cities and print their name and country to the console. The output will be different each time we run the program, because the cities are selected randomly.

πŸ“šCheck: GetItems<T>()

Shuffle<T>()

The Shuffle<T>() method is another new feature in .NET 8 that allows you to randomize the order of elements in a span.

This is important in machine learning instances when you wish to eliminate training bias by randomizing training data order.

Here’s an example that shows how to use Shuffle<T>() with an array of YourType objects:

YourType[] trainingData = LoadTrainingData();
Random.Shared.Shuffle(trainingData);

IDataView sourceData = mlContext.Data.LoadFromEnumerable(trainingData);

DataOperationsCatalog.TrainTestData split = mlContext.Data.TrainTestSplit(sourceData);
model = chain.Fit(split.TrainSet);

IDataView predictions = model.Transform(split.TestSet);
// ...

In this example, we load some training data into an array of YourType objects and use Random.Shared to shuffle the order of the elements.

We then load the shuffled data into an IDataView object, split the data into training and test sets, and use the shuffled training data to train a machine learning model. Finally, we use the trained model to make predictions on the test set.

πŸ“šCheck: Shuffle<T>()

Performance Improvements

.NET 8 introduces several new types that are focused on improving app performance. These new types are:

  • The System.Collections.Frozen namespace includes two collection types, FrozenDictionary<TKey,TValue> and FrozenSet<T>. These types do not allow any changes to keys and values once a collection is created, which allows for faster read operations such as TryGetValue(). They are particularly useful for collections that are populated on first use and then persisted for the duration of a long-lived service:
private static readonly FrozenDictionary<string, bool> s_configurationData =
    LoadConfigurationData().ToFrozenDictionary(optimizeForReads: true);
// ...
if (s_configurationData.TryGetValue(key, out bool setting) && setting)
{
    Process();
}
  • The System.Text.CompositeFormat type is useful for optimizing format strings that aren’t known at compile time. A little extra time is spent up front to do work such as parsing the string, but it saves the work from being done on each use:
private static readonly CompositeFormat s_rangeMessage = CompositeFormat.Parse(LoadRangeMessageResource());
// ...
static string GetMessage(int min, int max) =>
    string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);

The System.Buffers.IndexOfAnyValues<T> type is designed to be passed to methods that look for the first occurrence of any value in the passed collection. .NET 8 adds new overloads of methods like String.IndexOfAny and MemoryExtensions.IndexOfAny that accept an instance of the new type.

πŸ“šCheck: Performance-focused types

Native AOT

.NET 8 brings improvements to the native ahead-of-time (AOT) compilation feature that was first introduced in .NET 7. Publishing an application as native AOT generates a self-contained version of the app that doesn’t require a runtime as everything is included in a single file.

In addition to the existing support for various platforms, .NET 8 now includes support for the x64 and Arm64 architectures on macOS. This means that developers can now publish their .NET apps as native AOT for macOS systems.

The latest improvements to native AOT apps on Linux systems have resulted in significantly reduced application sizes.

According to recent tests, native AOT apps built with .NET 8 Preview 1 now take up to 50% less space compared to those built with .NET 7.

You can see in the table below a comparison of the size of a “Hello World” app published with native AOT and including the entire .NET runtime between the two versions:

Operating System.NET 7.NET 8 Preview 1
Linux x64 (with -p:StripSymbols=true)3.76 MB1.84 MB
Windows x642.85 MB1.77 MB

These improvements in native AOT can help .NET developers to create smaller, faster, and more efficient apps that run on a variety of platforms without requiring any external dependencies.

πŸ“šCheck: Native AOT

Code generation

.NET 8 also has some improvements in code generation and JIT (Just-In-Time) compilation enhancing performance and efficiency:

  • Arm64 architecture performance improvements
  • SIMD (Single Instruction Multiple Data) improvements for better vectorization and parallelization of operations
  • Cloud-native improvements for better performance in containerized environments
  • Profile-guided optimization (PGO) improvements that enable better optimizations based on application usage patterns
  • Support for AVX-512 ISA extensions for more efficient floating-point operations on modern CPUs
  • JIT (Just-In-Time) throughput improvements for faster code generation
  • Loop and general optimizations that improve the performance of frequently used code blocks

These improvements help developers to optimize the performance of their .NET applications and reduce resource utilization in cloud-native environments.

πŸ“šCheck: Code generation

.NET container images

.NET 8 also makes a few changes to the way .NET container images work. First, Debian 12 (Bookworm) is now the default Linux distribution in the container images.

Additionally, the images include a non-root user to make the images non-root capable. To run as non-root, add the line USER app at the end of your Dockerfile or a similar instruction in your Kubernetes manifests.

The default port has also changed from 80 to 8080 and a new environment variable ASPNETCORE_HTTP_PORTS is available to make it easier to change ports.

The format for the ASPNETCORE_HTTP_PORTS variable is easier compared to the format required by ASPNETCORE_URLS, and it accepts a list of ports. If you change the port back to 80 using one of these variables, it won’t be possible to run as non-root.

To pull the .NET 8 Preview SDK, you can use the following tag which includes the -preview suffix in the tag name for preview container images:

docker run --rm -it mcr.microsoft.com/dotnet/sdk:8.0-preview

The suffix -preview will no longer be used for release candidate (RC) releases. In addition, developers can use chiseled Ubuntu images with .NET 8 that offer a smaller attack surface, no package manager or shell, and non-root capability. These images are ideal for developers looking for the advantages of appliance-style computing.

πŸ“šCheck: .NET container images


So, are you excited to try out these new features in .NET 8? What do you think will be the most useful addition for your projects? Don’t hesitate to download the preview and explore the possibilities. Happy coding!

You May Also Like