Microsoft has recently announced the release of NET8, the latest version of its popular software development platform. NET8 brings many new features and improvements to help developers create modern applications for web, mobile, desktop, cloud, and IoT. In this blog post, we will present some of the highlights of NET8 and how they can benefit you as a developer.
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.
The new MAUI
One of the main goals of NET8 is to simplify and unify the development experience across different platforms and devices. NET8 introduces a new project system called NET Multi-platform App UI (MAUI), which allows you to build native UIs for Windows, Mac, Android, and iOS using a single codebase and project file.
You can use C# and XAML to design your UIs and share code and resources across platforms. NET MAUI also integrates with Visual Studio and Visual Studio Code, providing you with tools such as IntelliSense, debugging, hot reload, and hot restart.
Support Blazor WebAssembly AOT
Another key feature of NET8 is the support for Blazor WebAssembly AOT (ahead-of-time) compilation. Blazor is a framework that lets you build interactive web applications using C# and HTML. Blazor WebAssembly runs your C# code directly in the browser using a WebAssembly-based .NET runtime. With Blazor WebAssembly AOT, you can compile your C# code to native code before deploying it to the web server, resulting in faster startup times and better performance. Blazor WebAssembly AOT also enables you to use native libraries and interop with JavaScript.
Other improvements
NET8 also improves the performance and reliability of your applications by introducing new features such as Source Generators, global usings, file-scoped namespaces, implicit usings, minimal APIs, and improved garbage collection.
Source Generators are a new way to generate code at compile time based on your source code or metadata. They can help you reduce boilerplate code, optimize performance, and enhance tooling.
Global usings allow you to specify namespaces that are automatically imported in every source file in your project, saving you from typing them repeatedly.
File-scoped namespaces and implicit usings simplify the syntax of your C# code by reducing the indentation and removing unnecessary keywords. Minimal APIs enable you to create web APIs with minimal code and configuration using a new set of extension methods for ASP.NET Core. Improved garbage collection reduces memory usage and pauses by introducing new modes such as concurrent compacting GC and pinned object heap compaction.
These are just some of the exciting features that NET8 has to offer. If you want to learn more about NET8 and how to get started with it, you can visit the official website or check out the documentation. You can also download NET8 from this page or use Visual Studio 2023 or Visual Studio Code with the latest updates.
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.
Here the documentation on Microsoft Learn.
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 NET8.
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
};
// { "property_name" : "value" }
JsonSerializer.Serialize(new { PropertyName = "value" }, options);
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.
More info on the official documentation on 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.
More info on the official documentation on 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.
More info on the official documentation on 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>
andFrozenSet<T>
. These types do not allow any changes to keys and values once a collection is created, which allows for faster read operations such asTryGetValue()
. 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.
More info see the official documentation on 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 MB | 1.84 MB |
Windows x64 | 2.85 MB | 1.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.
More info see the official documentation on 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.
More info see to the official documentation on 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.