Announcing .NET 8 Preview 1

Jeremy Likness

Welcome to .NET 8! The first preview is ready for you to download: claim your copy of the first .NET 8 preview and start building applications today. Scroll down to see the list of features included in this preview. .NET 8 is a long-term support (LTS) release. This blog post covers the major themes and goals that drive the prioritization and selection of enhancements to develop. .NET 8 preview and release candidate builds will be delivered monthly. As usual, the final release will be delivered sometime in November at .NET Conf.

Download .NET 8 Preview 1

Releases of .NET include products, libraries, runtime, and tooling, and represent a collaboration across multiple teams inside and outside Microsoft. The broader themes covered in this blog post do not encompass all of the key scenarios and investments for .NET 8. They represent large areas but are just a part of all the important work going into .NET 8. We plan to make broad investments in ASP.NET Core, Blazor, EF Core, WinForms, WPF, and other platforms. You can learn more about these areas by reading the product roadmaps:

Be sure to check out themesof.net for more details on the GitHub issues and milestones being tracked towards .NET 8.

You can download .NET 8 Preview 1, for Windows, macOS, and Linux.

Stay current with what’s new and coming by reading our What’s New in .NET 8 documentation, which will be updated throughout the release. As our team releases new previews, we will include a summary of the known features in it.

.NET 8 has been tested with 17.6 Preview 1. We recommend that you use the preview channel builds if you want to try .NET 8 with the Visual Studio family of products. Visual Studio for Mac support for .NET 8 previews is not currently supported.

Welcome to .NET 8

At the end of last year, we shipped .NET 7, the result of a collaboration between the .NET team and the amazing community that supported the release with over 28,000 community contributions by over 10,000 community members. .NET 7 is the framework of choice for building applications today. The release unifies the platform with native support for ARM64 and enhanced support on Linux. It helps modernize your application through tools like .NET MAUI that enables building cross-platform mobile and desktop apps from the same codebase. It includes improvements to the performance of APIs and makes it easier to build and deploy distributed cloud native apps. .NET 7 simplifies the experience of building apps by reducing the amount of code necessary through improvements in C# 11 and making it possible to create and configure APIs with just a few lines of code. Numerous improvements to tooling from dev tunnels that help debug cloud API integrations to building containers directly from the .NET SDK help developers be more productive.

We will be updating What’s new in .NET 8 throughout the release. It will describe they key features for the whole release, while the blog posts will focus on new features in each preview release.

You can read about what we shipped in preview 1 by scrolling down. First, let’s look ahead at what the vision for .NET 8 is.

The best platform and tools for cloud native developers

We believe .NET developers should be able to get their apps to the cloud quickly, scale their apps without compromising performance, and evolve their apps based on actionable data and feedback about your apps in production. We’ll invest in making it easier to manage the full end-to-end experience from local development and testing through continuous integration and deployment. Our goal is to make it easier to implement microservice architectures and build and deploy containers.

Cloud native is a term used to describe the architecture and design of applications that are built specifically for deployment in cloud computing environments. The main idea behind cloud native is to take advantage of the benefits provided by cloud computing platforms, such as scalability, elasticity, and self-healing, to create highly scalable and resilient applications. This allows flexibility and avoids potential over-investing in hardware and software to support growth. Many developers associate cloud native with concepts such as microservices, container orchestration (Kubernetes) and “-as-a-service” offerings.

A great experience using MAUI and Blazor hybrid for cross-platform mobile and desktop development

During the .NET 7 timeframe we released .NET Multi-platform App UI (MAUI) SDK and Visual Studio tooling support. .NET MAUI provides a framework for creating native apps for mobile and desktop devices that run Android, iOS, macOS and Windows with a single C# codebase. In addition to support for XAML UI, you can also use Blazor to build hybrid apps with Razor UI components that can access the native device platforms and be shared across mobile, desktop, and web. The .NET team plans to build on these experiences and focus on improving the quality, stability, performance and integration of the SDK and tooling.

Momentum: continued focus on quality and performance based on your input

Every release of .NET includes improvements to performance, quality, stability, and ease of use of the APIs, libraries and frameworks the make up the active and growing .NET ecosystem. Many of these improvements were identified and prioritized by customers and community members. .NET 8 will follow the same trend, relying on your highly valued feedback to help guide our vision and drive our focus.

Get current and stay current

The .NET upgrade assistance is a valuable tool that helps developers migrate their applications from older versions of the .NET Framework to newer versions. The latest version of this tool comes with improved capabilities that support new scenarios and handle more cases. With this tool, developers can now upgrade their applications to .NET 6 or .NET 7 with ease.

The tool can automatically detect and suggest changes that need to be made to the code to ensure compatibility with the newer versions of the framework. Additionally, it can handle more complex scenarios, such as upgrading applications that use third-party libraries and integrating with newer platform features. These improvements make the .NET upgrade assistance an indispensable tool for developers looking to keep their applications up-to-date and take advantage of the latest .NET features. This tooling has recently been introduced as a Visual Studio extension to help you upgrade from the comfort of Visual Studio.

Targeting .NET 8

To target .NET 8, you first need to ensure you have the .NET 8 SDK installed from the official Microsoft website. Next, you can create a new project and specify that you want to target .NET 8 by setting the appropriate target framework in your project settings.

You can also update an existing project to target .NET 8 by changing the target framework in the project properties. To do this, right-click on the project in Visual Studio or your preferred IDE, select “Properties”, and then select the “Application” tab. From there, you can choose the target framework version that you want to use. This will set the appropriate target framework:

<TargetFramework>net8.0</TargetFramework>

Keep in mind that targeting .NET 8 may require changes to your code or dependencies, as there may be changes in APIs or other features from previous versions of .NET. It is a good idea to review the documentation and release notes for .NET 8 to ensure that your code and dependencies are compatible with the new version.

What’s New in .NET 8 Preview 1

Our first preview is packed with new features you can try out today. Here is a summary of what to expect. For detailed release notes and breaking changes, please read What’s new in .NET 8.

Native AOT

The first NativeAOT features were shipped in .NET 7 and targeted console applications. Ahead-of-Time (AOT) compilation is an important feature in .NET that can have a significant impact on the performance of .NET applications. Thanks to Adeel and Filip for bringing NativeAOT capabilities to macOS for preview 1. The .NET team will focus on refining some of the fundamentals for .NET 8 such as size (see dotnet/runtime#79003). Publishing app with Native AOT creates a fully self-contained version of your app that doesn’t need a separate runtime because everything is included in a single file. As of preview 1, this single file is smaller. In fact, Linux builds are now up to 50% smaller.

Here are the sizes of a “Hello, World” app with Native AOT that includes the entire .NET runtime:

.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

NativeAOT will continue to expand and target other application scenarios in .NET 8 so keep watching this blog for future updates!

In case you’re not familiar with AOT, here are a few benefits AOT provides:

  • Reduced memory footprint: AOT compiled code requires less memory compared to JIT compiled code, as the JIT compiler generates intermediate code that is not needed in AOT compiled applications. This can be especially beneficial for devices with limited memory, such as embedded systems and mobile devices.

  • Improved startup time: AOT compiled code starts up faster compared to JIT compiled code, as it eliminates the need for the JIT compiler to generate intermediate code and optimize the code for the specific hardware and software environment. This can be especially beneficial for applications that have to start up quickly, such as system services, serverless “functions” and background tasks.

  • Improved battery life: AOT compiled code consumes less power compared to JIT compiled code, as it eliminates the need for the JIT compiler to generate intermediate code and optimize the code for the specific hardware and software environment. This can be especially beneficial for devices that rely on batteries, such as mobile devices.

.NET Container images

.NET developers can use container images to package and deploy their applications in a lightweight, portable format that runs across different environments and can be easily deployed to the cloud. Preview 1 includes the following improvements in how container images can be used for .NET applications:

Update default Linux distro to Debian 12:.NET container images now use Debian 12 (Bookworm), which we expect to be released mid-2023. Debian is used for convenience tags like 8.0 and Debian-specific tags like 8.0-bookworm-slim.

Tagging change: .NET 8 preview container images will use the 8.0-preview tag (not 8.0) and transition to 8.0 with the Release Candidate releases. The goal of this approach is to more clearly describe preview releases as such. This change was made based on a community request.

Run container images with non-root users: Though container base images are almost always configured to run with the root user – a setting which tends to be kept in production – it is not always the best approach. It is a pain to configure each application to have a different user, however, and container images do not come with a non-root user that is appropriate for container workloads.

.NET 8 offers a better way. Starting with Preview 1, all container images we publish will be non-root capable. Here is an example of the single line used to run a container as non-root for Dockerfiles:

USER app

In addition, you can now launch container images with -u app. The default port has changed from port 80 to 8080. This is a breaking change that was necessary in order to enable the non-root scenario, since port 80 is a privileged port.

Runtime and libraries

Utility methods for working with randomness

Both System.Random and System.Security.Cryptography.RandomNumberGenerator have gained utility methods for randomly choosing items from the input set (“with replacement”), called GetItems, and for randomizing the order of a span, called Shuffle.

Shuffle is useful in reducing training bias in Machine Learning (so the first thing isn’t always training, and the last thing always test):

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

Shall we play a game? How about Simon?

private static ReadOnlySpan<Button> s_allButtons = new[]
{
    Button.Red,
    Button.Green,
    Button.Blue,
    Button.Yellow,
};

...

Button[] thisRound = Random.Shared.GetItems(s_allButtons, 31);
// rest of game goes here ...

System.Numerics and System.Runtime.Intrinsics

We reimplemented Vector256<T> to internally be 2x Vector128<T> ops where possible: dotnet/runtime#76221. This allows partial acceleration of some functions when Vector128.IsHardwareAccelerated == true but Vector256.IsHardwareAccelerated == false, such as on Arm64.

Added the initial managed implementation of Vector512<T>: dotnet/runtime#76642. Much like the previous work item, this is implemented internally as 2x Vector256<T> ops (and therefore indirectly as 4x Vector128 ops). This allows partial acceleration of some functions even when Vector512.IsHardwareAccelerated == false. — NOTE: There is no direct acceleration for Vector512 yet, even when the underlying hardware supports it. Such functionality should be enabled in a future preview.

Rewrote Matrix3x2 and Matrix4x4 to better take advantage of hardware acceleration: dotnet/runtime#80091. This resulted in up to 48x perf improvements for some benchmarks. 6-10x improvements were more common. — NOTE: Improvements to Quaternion and Plane will be coming in Preview 2

Hardware Intrinsics are now annotated with the ConstExpected attribute: dotnet/runtime#80192. This ensures that users are aware when the underlying hardware expects a constant and therefore when a non-constant value may unexpectedly hurt performance.

Added the Lerp API to IFloatingPointIeee754<TSelf> and therefore to float (System.Single), double (System.Double), and System.Half: dotnet/runtime#81186. This allows efficiently and correctly performing a linear interpolation between two values.

JSON improvements

We keep improving System.Text.Json, focusing on the performance and reliability enhancement of the source code generator if it’s used together with ASP.NET Core in NativeAOT applications. The following list shows the new features shipped with Preview 1:

  • Missing member handling dotnet/runtime#79945

    It’s now possible to configure object deserialization behavior, whenever the underlying JSON payload includes properties that cannot be mapped to members of the deserialized POCO type. This can be controlled by setting a JsonUnmappedMemberHandling value, either as an annotation on the POCO type itself, globally on JsonSerializerOptions or programmatically by customizing the JsonTypeInfo contract for the relevant types:

    JsonSerializer.Deserialize<MyPoco>("""{"Id" : 42, "AnotherId" : -1 }"""); 
    // JsonException : The JSON property 'AnotherId' could not be mapped to any .NET member contained in type 'MyPoco'.
    
    [JsonUnmappedMemberHandling(JsonUnmappedMemberHandling.Disallow)]
    public class MyPoco
    {
       public int Id { get; set; }
    }
  • Source generator support for required and init properties dotnet/runtime#79828

    The source generator now supports serializing types with required and init properties, as is currently supported in reflection-based serialization.

  • Interface hierarchy support dotnet/runtime#78788

    System.Text.Json now supports serializing properties from interface hierarchies:

    IDerived value = new Derived { 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 Derived : IDerived
    {
      public int Base { get; set; }
      public int DerivedProp { get; set; }
    }
  • Snake Case and Kebab Case dotnet/runtime#69613

    The library now ships with naming policies for snake_case and kebab-case property name conversions. They can be used similarly to the existing camelCase naming policy:

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

    The following naming policies are now available:

    namespace System.Text.Json;
    
    public class JsonNamingPolicy
    {
      public static JsonNamingPolicy CamelCase { get; }
      public static JsonNamingPolicy KebabCaseLower { get; }
      public static JsonNamingPolicy KebabCaseUpper { get; }
      public static JsonNamingPolicy SnakeCaseLower { get; }
      public static JsonNamingPolicy SnakeCaseUpper { get; }
    }

    Thanks, @YohDeadfall for contributing the implementation.

  • Add JsonSerializerOptions.MakeReadOnly() and JsonSerializerOptions.IsReadOnly APIs dotnet/runtime#74431

    The JsonSerializerOptions class has always been using freezable semantics, but up until now freezing could only be done implicitly by passing the instance to one of the JsonSerializer methods. The addition of the new APIs makes it possible for users to explicitly control when their JsonSerializerOptions instance should be frozen:

    public class MySerializer
    {
      private JsonSerializerOptions Options { get; }
    
      public MySerializer()
      {
            Options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { Converters = { new MyCustomConverter() } };
            Options.MakeReadOnly(); // Make read-only before exposing the property.
      }
    }

New performance-focused types in the core libraries

Multiple new types have been added to the core libraries to enable developers to improve the performance of their code in common scenarios.

The new System.Collections.Frozen namespace provides FrozenDictionary<TKey, TValue> and FrozenSet<T> collections. These types provide an immutable surface area such that, once created, no changes are permitted to the keys or values. That in turn enables the collections to better optimize subsequent read operations (e.g. TryGetValue) based on the supplied data, choosing to take more time during construction to optimize all future accesses. This is particularly useful for collections populated on first use and then persisted for the duration of a long-lived service, e.g.

private static readonly FrozenDictionary<string, bool> s_configurationData =
    LoadConfigurationData().ToFrozenDictionary(optimizeForReads: true);
...
if (s_configurationData.TryGetValue(key, out bool setting) && setting)
{
    Process();
}

The existing ImmutableArray<T>.Builder type also gained a new method for converting its contents into an ImmutableArray<T> efficiently. .NET 8 introduces DrainToImmutable(), which will return the current contents as an immutable array and reset the builder’s collection to a zero-length array, choosing the most efficient approach for doing so. This method can be used instead of conditionally calling ToImmutable() or MoveToImmutable() based on the count of elements.

Another example of a new type that helps a developer to invest a bit of time upfront in exchange for much faster execution later is the new IndexOfAnyValues<T> type. In addition to new methods like IndexOfAnyInRange, new overloads of IndexOfAny have been added that accept an IndexOfAnyValues<T> instance, which can be created to represent a set of T values for which to search. The creation of this instance handles deriving whatever data is necessary in order to optimize subsequent searches. For example, if you routinely search for all ASCII letters and digits plus a few punctuation characters, you might previously have written:

private static readonly char[] s_chars = "-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".ToCharArray();
...
int i = str.IndexOfAny(s_chars);

That, however, requires either not doing any kind of vectorization to improve the efficiency of the search, or it involves taking time on each invocation of IndexOfAny to compute the necessary state to speed up the operation. Now instead, it can be written as:

private static readonly IndexOfAnyValues<char> s_chars = IndexOfAnyValues.Create("-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz");
...
int i = str.AsSpan().IndexOfAny(s_chars);

precomputing all of that state once such that it’s available for reuse on every subsequent IndexOfAny invocation.

This pattern repeats itself again with the new CompositeFormat type. .NET has long had support for string formatting via APIs like string.Format and StringBuilder.AppendFormat, e.g.

static string GetMessage(int min, int max) =>
    string.Format(CultureInfo.InvariantCulture, "Range from {0} to {1}", min, max);

C# 6 added support for string interpolation, and then C# 10 in conjunction with .NET 6 significantly improved the efficiency of these operations, enabling the same operation to be written as:

static string GetMessage(int min, int max) =>
    string.Create(CultureInfo.InvariantCulture, $"Range from {min} to {max}");

but doing all of the work that can be precomputed (e.g. parsing the format string) at compile time rather than on each invocation of string.Format. That, however, requires the format string to be known at compile time so it can be parsed at compile time… what if it’s not known until run-time, such as if it’s loaded from a resource file or some other dynamic means? For that, .NET 8 adds the CompositeFormat type. Just as with IndexOfAnyValues<T>, it enables taking an operation that would otherwise need to be done on each use and lifting it out to be done once.

private static readonly CompositeFormat s_rangeMessage = CompositeFormat.Parse(LoadRangeMessageResource());
...
static string GetMessage(int min, int max) =>
    string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);

These new overloads also support generic arguments, to avoid boxing overheads associated with taking everything as object.

.NET 8 Preview 1 also adds support for new performance-focused hashing algorithms, including the new XxHash3 and XxHash128 types that provide implementations of the fast XXH3 and XXH128 hash algorithms.

.NET SDK

dotnet publish and dotnet pack produce Release assets by default

Publish and pack verbs are intended to produce production assets, which means that they should produce Release assets. In .NET 8, they will do that by default. People have been asking that for some time. Sorry that it took so long!

This feature is controlled by the PublishRelease and PackRelease boolean properties. They default to true.

It is easiest to demonstrate the feature, with dotnet publish:

/app# dotnet new console
/app# dotnet build
  app -> /app/bin/Debug/net8.0/app.dll
/app# dotnet publish
  app -> /app/bin/Release/net8.0/app.dll
  app -> /app/bin/Release/net8.0/publish/
/app# dotnet publish -p:PublishRelease=false
  app -> /app/bin/Debug/net8.0/app.dll
  app -> /app/bin/Debug/net8.0/publish/

Note that PublishRelease and PackRelease also exist in .NET 7 starting with the 7.0.200 SDK. They are opt-in in .NET 7 and must be set to true to provide the same behavior.

See the breaking change docs:

Linux support

Build your own .NET from dotnet/dotnet

.NET is now buildable on Linux directly from the dotnet/dotnet repository. It uses dotnet/source-build to build .NET runtimes, tools and SDKs. This is the same build that Red Hat and Canonical use to build .NET, for example. Over time, we will extend it to support macOS and Windows.

See build instructions to build the VMR on your own machine. Building in a container will be the easiest approach for many people, since our dotnet-buildtools/prereqs container images contain all required dependencies.

We’re calling this new repository a Virtual Mono Repository (VMR). It has the benefits of a true monorepo but is a regularly-updated projection of the many existing repos that contributors work in (more efficiently) ever day. We believe that the split between the VMR and the much smaller “working repos” is the future of the .NET project. We expect that cross-cutting features will be easier to build in the VMR, however, we’re not quite that far along yet.

We see this new approach as being a significant step forward in approachability for building .NET as a whole product from source.

Prior to .NET 8, building from source was possible, but required creation of a “source tarball” from the dotnet/installer commit that corresponded to a release. This is no longer necessary. The repository will have tags corresponding to each release, as well as main and release/8.0-previewN branches which continually track the state of the product.

.NET 8 + Ubuntu Chiseled container images

We are publishing Ubuntu Chiseled images with .NET 8. This type of image is for developers that want the benefit of appliance-style computing, even more so than you get with regular containers. We expect that Ubuntu chiseled images will be supported in production by both Canonical and Microsoft by the time .NET 8 ships.

We plan to ship to dotnet/monitor images exclusively as Ubuntu Chiseled, starting with .NET 8. That’s notable because the monitor images are the one production app image we publish.

Chiseled images have a lot of benefits:

  • Ultra-small images (reduced size and attack surface)
  • No package manager (avoids a whole class of attacks)
  • No shell (avoids a whole class of attacks)
  • Non-root (avoids a whole class of attacks)

You can see the pattern for producing chiseled images, with our aspnetapp sample. It only requires a one-line change.

Chiseled images are currently published to our nightly repos, for .NET 6 and .NET 7 versions.

Linux support and baseline target

We are updating our minimum baselines for Linux for .NET 8. There are three notable changes.

  • The .NET product will be built targeting Ubuntu 16.04, for all architectures. That’s primarily important for defining the minimum glibc version for .NET 8. For example, .NET 8 will fail to even start on Ubuntu 14.04, for example, due to this change.
  • For Red Hat Enterprise Linux (RHEL), we will support RHEL 8+, dropping RHEL 7.
  • We will only publish a support statement for RHEL, however, we intend that support to apply to other RHEL ecosystem distros.

There are no other significant changes. We will continue to support Linux on Arm32, Arm64, and x64 architectures.

Note that these changes only apply to the Microsoft build. Organizations using source-build will make different choices, typically producing one build for and that only works with one distro version, like Ubuntu 24.04.

The following demonstrates the Ubuntu 16.04 glibc version and a pattern for discovering it for other distros.

$ docker run --rm ubuntu:16.04 ldd --version
ldd (Ubuntu GLIBC 2.23-0ubuntu11.3) 2.23
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.

CodeGen

Community PRs (Many thanks to JIT community contributors!)

  • @am11 updated the env variable lookup in clrconfig and jithost in PR#77025.
  • @En3Tho optimized (X & 1) != 0 to (X & 1) and (X & 1) == 0 to ((NOT X) & 1) in PR#74806, and fixed a bug related to a faulty LIR range in PR#77166.
  • @MichalPetryka implemented Type.IsEnum and Type.GetEnumUnderlyingType intrinsics in PR#71685, and converted MemoryMarshal.GetArrayDataReference to a JIT intrinsic in PR#79760.
  • @pedrobsaila made the first PR contribution in PR#62689 to emit branchless form of (x >= 0 && y >= 0) to (x | y) >= 0 and (x != 0 && y != 0) to (x | y) != 0.
  • @a74nh, @AndyJGraham and @swapnilGaikwad made contributions on Arm64 perf improvements (see Arm64 section).
  • @DeepakRajendrakumaran made contributions to AVX-512 effort (see AVX-512 section).
  • @SingleAccretion made 72 PR contributions in Preview 1, among them:

Cloud Native

  • PR#79709 removed helper calls for non-gc static fields, and PR#80969 optimized static fields of gc types.
  • PR#80831 devirtualizes casts to interfaces with single implementations.

Arm64

Arm64 performance improvement work is ongoing as planned in Issue#77010.

  • @AndyJGraham and @a74nh implemented peephole optimizations related to ldp and stp in PR#77540.
  • @a74nh enabled If Conversion in PR#73472, PR#77728, PR#78223, and PR#77888.
  • @swapnilGaikwad used zero register in conditional select (csel) in PR#78330.
  • @SwapnilGaikwad enabled mneg in PR#79550, one of the multi-op instructions (Issue#68028).
  • Faster Vector comparisons via PR#75864 and PR#75999.
  • PR#75823 added support for “shifted register” operations on Arm64.

AVX-512

.NET 8 will support the AVX-512 ISA extensions as planned in Issue#77034.

  • PR#76642 implemented APIs to expose Vector512<T> types as defined in Issue#73262.
  • AVX-512 state support was added in VM by @DeepakRajendrakumaran in PR#74113.
  • @DeepakRajendrakumaran added EVEX encoding support for emitOutput* paths in PR#75934, PR#77419 and PR#78044.
  • JitInterface was updated to support Vector512 by @DeepakRajendrakumaran in PR#81197.

General SIMD improvements

  • PR#77562 implemented additional intrinsics for newly approved APIs on Vector64/128/256/512<T> and Vector<T> defined in Issue#76593.
  • PR#79720 implemented Vector2/3/4 and Vector<T> using HWIntrinsics.
  • PR#77947 vectorized String.Equals for OrdinalIgnoreCase.

PGO

Fundamental PGO improvements are in progress as planned in Issue#74873.

  • A new JIT tier is introduced to instrument only hot Tier0 and R2R code. This means you no longer need to disable ReadyToRun and sacrifice startup time to have Full PGO level of performance benefits: PR#70941
  • PR#80481 enabled edge based profiles for all scenarios.
  • Building pred list has moved to very early stage of JIT: PR#80625, PR#80856, PR#81288, PR#81246, PR#81196, PR#81000, and PR#80891.

Loop Optimizations

  • PR#75140 supports delegate GDV guards in loop cloning.
  • PR#80353 extended loop unrolling optimization

General Optimizations

  • @SingleAccretion enabled promotion of multi-reg Long variables on 32 bit unblocking field enregistration in PR#76263.
  • PR#77103 implemented a new tail merging optimization as proposed in Issue#8795.
  • PR#81055 ensures that Span<T>.Length and ROSpan<T>.Length are recognized as “never negative”.
  • PR#79346 added an early liveness pass that allows the JIT to remove a lot of unnecessary struct copies when passing struct arguments.
  • PR#77874 removed unnecessary zero/sign-extending instructions for some simple operations involving small integer types.
  • String literals, typeof(), static fields are moved to Non-GC Heap via PR#49576, PR#75573, and PR#76112.
    • It allowed JIT to omit GC write barriers in certain cases PR#76135.
    • typeof(..) no longer needs a helper call in most cases, e.g.:
      Type GetMyType() => typeof(string);
      
      ; Method MyType():System.Type:this
    • 4883EC28 sub rsp, 40
    • 48B918083857FC7F0000 mov rcx, 0x7FFC57380818
    • E80DE8AB5F call CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE
    • 90 nop
    • 4883C428 add rsp, 40
    • 48B8B800000364020000 mov rax, 0x264030000B8 ; ‘System.String’ C3 ret
      -; Total bytes of code: 25 +; Total bytes of code: 11

  • A batch of improvements for static and static readonly fields PR#77102, PR#77593, PR#77354, PR#77737, PR#78593, PR#78736 and PR#78783
  • A good example of what JIT is now capable of folding in .NET 8 is the expression shown in PR#81005.

JIT Throughput Improvements

  • PR#80265 converted JitHashTable iteration to range-based for to improve throughput by 0.22%.

Community spotlight (Youssef Victor)

.NET would not be as expansive as it is without the wealth of thoughtful contributions from the .NET community. Special shout out to this post’s community spotlight member @Youssef Victor, who shares his experience as a contributor and Microsoft MVP.

Youssef Victor, Community Spotlight Member

In Youssef’s own words:

“I’m Youssef Victor, an undergraduate in the Computers and Communications department, Alexandria University. I started contributing to the .NET organization (the dotnet/docs and dotnet/samples repos, to be specific) in 2019. It all started by chance when I found some samples not following .NET coding guidelines and found the little edit button in docs.microsoft.com (now, learn.microsoft.com). I decided to go ahead and fork the repo and make some changes and was amazed by how the team communicates very well with contributors and how fast they merge PRs! I continued contributing, trying to help improve the documentation as much as I could.

“Later, I looked into contributing product code, and found that dotnet/roslyn and dotnet/roslyn-analyzers are interesting repos. The team was also very supportive the same way the docs team is. They helped me a lot during my contribution journey. For the dotnet/roslyn-analyzers repo, I’ve helped to write new analyzers and code fixes per existing issues, created issues for new analyzer suggestions, fixed bugs, reviewed code, and also recently helped to triage issues. For the dotnet/roslyn repo, some of my work was in the compiler, including a C# 10 language feature called CallerArgumentExpression. I also fixed some bugs.

“The remainder of my work is on the IDE side, including IntelliSense issues, highlighting issues, implementing productivity features for new language features, etc. It’s been an incredible experience. Also, my contributions were recognized, and I was nominated and selected to be a Microsoft MVP!”

Closing

In conclusion, .NET 8 Preview 1 is a testament to the power of collaboration between a diverse team of engineers at Microsoft and a highly engaged open source community. The new features and improvements in .NET 8 are a direct result of the hard work and dedication of this community, and we are incredibly grateful for everyone’s contributions.

We are proud to be part of a community that values inclusivity and diversity, and we are committed to building a technology ecosystem that is accessible to everyone. We believe that by working together, we can achieve great things, and we are excited to see what the future holds for .NET.

We want to extend our sincere thanks to everyone who has contributed to .NET 8 so far, whether it was through code contributions, bug reports, or providing feedback. Your contributions have been instrumental in making .NET 8 Preview 1 a reality, and we look forward to continuing to work together to build a brighter future for .NET and the entire technology community.

68 comments

Discussion is closed. Login to edit/delete existing comments.

  • Paulo Pinto 0

    All great annoucements, specially the numerics stuff.

    Regarding NativeAOT, any plans to improve the experience across the various Visual Studio workloads?

    Having to go into the CLI to publish binaries feels like a lesser experience, compared with .NET Native experience or other AOT compiled languages with IDE tooling.

    • Eric ErhardtMicrosoft employee 0

      Yes, we plan to make the NativeAOT publishing experience better in Visual Studio.

      You can publish for NativeAOT in VS today by following these steps:
      1. Add <PublishAot>true</PublishAot> to the top PropertyGroup in your .csproj.
      2. Right-click the project and “Publish”.
      3. Select “Folder” publish.
      4. In the Publish Settings, change the Target Runtime from Portable to win-x64 (or whatever runtime you are targeting).
      5. Click the “Publish” button.

      • Paulo Pinto 0

        Thanks for taking developer usability into account.

        Maybe parallel to the VS improment effors, these steps could be part of Native AOT documentation?

  • Andreas Heisel 1

    What ist the point of “System.Collections.Frozen” compared to “System.Collections.Immutable”?

    • Mark Adamson 0

      I was also a bit confused by that section. I don’t really get the distinction or the reason for using the word Frozen instead of Immutable or how these differ from just saying .ToImmutableList(). Should we be considering the immutable collections legacy with this change?

      I also didn’t understand the DrainToImmutable thing, it sounded like it functionally does the same as MoveToImmutable, but perhaps I’ve misunderstood.

      • Michael Taylor 0

        I don’t use immutable collections myself but I believe the rationale is that immutable collections are immutable from the moment they are created. That is one reason I don’t use them. In general I have a mutable list that I’m adding/adjusting. Once I’m done then I want to lock the collection down for changes. You cannot do that with immutable. You have to create your original version, then pass that to one of the create methods of the immutables which requires an array I believe. So you have the overhead of converting your collection of items to an array so hopefully the collection isn’t too large. The alternative To... methods take an IEnumerable but since they cannot guarantee you’re not going to change things they have to probably copy the items into an array so you’re always taking the hit of allocating an array (or similar) and copying items which can be expensive in terms of time and memory for large sets of data.

        The frozen concept (which exists in other places in the framework) allows you to keep your existing collection of items and then freeze them in place once you’re done creating them. Since they are frozen you don’t need to allocate more memory and it is probably just a boolean flag so perf is fine whether there is 1 or 1000 items in the collection.

        Could they have combined this freeze functionality into the existing immutables? Possibly but since I don’t use them I cannot determine the impact that might have had.

    • Stephen Toub - MSFTMicrosoft employee 2

      The immutable collections are “immutable” in that a given instance of a collection can’t be changed. However, they do encourage mutation: not mutation of that instance, but creating new instances derived from the original. So you’ll see methods like Add and Remove on the immutable collections, which don’t mutate the original instance but instead create a new instance that contains that mutation (you’ll often see such collections described as “persistent collections” in literature). The net result of that, beyond having a surface area that encourages such operations, is the implementations are optimized for such use, in particular around the sharing of memory between instances. If you have an ImmutableDictionary<string, string>, under the covers it’s a tree data structure, and when you “Add” to it, it creates a new ImmutableDictionary<string, string> that shares as much of the original tree as possible but with the additional nodes necessary to support the added item. This means even though ImmutableDictionary<> is “immutable”, it’s not actually optimized for fast reading, e.g. a TryGetValue is an O(log n) operation.

      The new FrozenSet<>/FrozenDictionary<> on the other hand are truly immutable; there’s no API surface area that allows or encourages additions, removals, etc. And as such, they are designed for the only operations you can perform on them: reading. If all you care about is not having any APIs that enable any form of mutation, then you can just use ToFrozenSet/Dictionary. If you plan on having these be around for a while and want to spend more time at construction to put the data into a form that makes them faster for all subsequent reads, you can pass a optimizeForReading:true bool to those methods, and the factory will take more time computing the best storage it can to make subsequent operations like TryGetValue really fast. It needn’t worry about storing the data in a way that will make it cheap to create derived collections because there’s no surface area for performing such operations.

      • Mark Adamson 0

        Thanks, that is useful. I’ve just re-read and see there will only be FrozenSet and FrozenDictionary, which might give me a difficult choice given the style I encourage in our team.

        I don’t actually make use of any of the adders for the Immutable collections, I just use them to enforce a functional style with pure functions etc. So FrozenSet and FrozenDictionary will be very tempting given the benefits you highlight. However, I’ll then have a mixture of Frozen... and Immutable... types which feels a bit yukky and will be difficult to communicate. Can we get FrozenArray as well, providing an alternative to ImmutableArray?

      • Igor VelikorossovMicrosoft employee 0

        Thank you Stephen. Could you please make sure this gets into the docs as well?

      • Mark Adamson 0

        ok, so my request for FrozenArray got rejected, I can understand that. Alternatively, how about taking a similar principle to IndexOfAnyValues and providing a way for the developer to steer the performance characteristics of ImmutableSet and ImmutableDictionary instead of introducing new types? It would be similar to the case where we can provide the initial length of List, it doesn’t change the interface, but it allows for fine-tuning performance when needed.

        So it could be a new boolean parameter in .ToImmutableDictionary etc., that would specify optimiseForFrozen or a better name :).

  • Alexey Leonovich 0

    According the docs .NET 8 supports Debian 11+. Can you confirm that Debian 10 support is dropped? It is current LTS release which is supported by LTS Team till June 30th, 2024.
    According Support changes from .NET 6.0 section in the same doc only Windows Client 7 SP1 and 8.1 are dropped. So which one statement is correct? What’s new in .NET 8 doc also have no info about dropping Debian 10 support. So where is the truth?
    P. S. Install the .NET SDK or the .NET Runtime on Debian doc has no info about .NET 8 Preview 1 at all. I’ve tried instructions from Debian 10 section but apt doesn’t show me dotnet-sdk-8.0.

    • Richard LanderMicrosoft employee 0

      The “what’s new” content is a very basic summary and doesn’t include all information. We’ll make sure to get that updated for next time to be more specific (or link to our support docs).

      Those package feeds only support stable versions, not previews.

      Right, Debian 10 and windows 7 SP1 and 8.1 support have been dropped for .NET 8.

  • Michal Dobrodenka 0

    Publishing self contained trimmed version of my app fails with:

    error NETSDK1195: Unable to optimize assemblies for size: a valid r
    untime package was not found. Either set the PublishTrimmed property to false, or use a supported target framework when publishing.

    with any runtime: linux-arm, linux-arm64, win-x64…

    • Eric ErhardtMicrosoft employee 0

      This sounds like a bug. Can you open an issue at https://github.com/dotnet/runtime/issues/new/choose describing all the steps you did to get this error message? The more information you can give, the easier the team will be able to find the problem.

      • Michal Dobrodenka 0

        I got it. I had several netstandard2.1 class libraries in my solution. Is it bug or feature – that it can’t no longer trim netstd2.1? It’s now compiling, when I change it to net8.0

  • André Verwijs 0

    I hope also for Linux in combination with mono to build BIG projects
    without “developer packages” requirements (or make them available) right now .NET for linux is only a paper weigh
    you can’t make any serious use of it… maybe for the new Opensuse Leap 15.5 or OpenSUSE ALP….

    • Richard LanderMicrosoft employee 0

      Can you elaborate? What do you mean by “developer packages”?

  • ygc369 0

    I have not seen any new feature for C# 12. Will it be posted in next blog?
    Is safe fixed-size buffer of any type planned in C# 12?
    Will escape analysis be implemented in .NET 8?

  • Kevin Lecouvey 0

    Does the final version will be fully supported by VS 2022?

    Because the preview of .Net 6 was supported by VS 2019, but the final version required VS 2022.

    • Alexey Leonovich 0

      Have exactly the same question. My proposal is that every Visual Studio version should support .NET LTS version that is actual at the IDE release date AND the following .NET LTS version. Regarding Visual Studio 2022 it will be .NET 6.0 LTS -> .NET 7.0 -> .NET 8.0 LTS. Every .NET LTS version is supported for 3 years so at least one .NET target platform will be definitely supported during the whole Mainstream Support phase (5 years) of an IDE. Hope @timheuer and @richlander will hear me and understand my argumentation (since my proposal is pretty evident and simple)..

  • neon-sunset 0

    Fingers crossed Dynamic PGO will get enabled by default by the time .NET 8 is released 👀

    • Andy AyersMicrosoft employee 1

      We’ve been steadily working on improving PGO with that goal in mind.

      We’ve made it easier to enable Dynamic PGO. In .NET 6 you had change three configuration settings to get best performance; in .NET 7 you had to change two settings; and in .NET 8 you now only have to change one. Whether we can flip that one to be opt-out instead of opt-in is still to be determined.

      At a more detailed level there has been a lot of work to improve fundamental aspects like the efficiency of our instrumentation. Keep an eye on https://github.com/dotnet/runtime/issues/76969 and https://github.com/dotnet/runtime/issues/74873.

  • a b 0

    Container images now use Debian 12 (Bookworm) the LTS (Long-term support) version that shipped earlier this year.

    Are you absolutely sure that it has shipped?

    • Richard LanderMicrosoft employee 0

      It has not. Good catch. I fixed the content.

  • Khalid Abuhakmeh 1

    Great post, thanks for sharing plans for .NET 8 and some samples.

    I noticed that the JSON sample does not compile, because a member cannot share the name of the entity encapsulating it.

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

    Either the property name needs to change or the class name.

    Cheers 🙂

Feedback usabilla icon