The release of .NET 8 brings a whole host of new changes and improvements to the .NET ecosystem. As the successor to .NET 7, .NET 8 is a long-term support (LTS) release, which promises the developers with a stable platform and support for three years. This article aims to walk senior developers through essential updates in .NET 8, addressing various enhancements and features spanning SDK changes, serialization improvements, Core .NET libraries enhancements, and more. With this comprehensive insight, developers can explore the cutting-edge capabilities of .NET 8 and efficiently adapt to the ever-evolving world of software development.
We read through the changes and are bringing you the compressed version.
.NET SDK Changes
Terminal build output
To produce modernized build output, .NET 8 adds a new option to the 'dotnet build' command. This improved terminal logger output not only groups errors by their originating projects, but it also differentiates between target frameworks for multi-targeted projects. It also provides real-time information about the construction process. Use the --tl option to enable this new output.
Simplified output paths
With .NET 8, you can simplify the output path and folder structure for build outputs. Previous versions had a complex set of output paths for various build artifacts; the new format groups all build outputs into a single location, making it easier to manage for various tooling. Use the ArtifactsPath or UseArtifactsOutput properties in your Directory.Build.props file to enable this output path format.
'dotnet workload clean' command
A new command, 'dotnet workload clean,' has been introduced in .NET 8 to clean up workload packs left over from multiple .NET SDK or Visual Studio updates. This command helps restore known states in case of issues while managing workloads. The command features two modes: a standard mode for running workload garbage collection and a more aggressive mode for cleaning every pack on the machine.
'dotnet publish' and 'dotnet pack' assets
In .NET 8, the 'dotnet publish' and 'dotnet pack' commands now produce Release assets by default. This change ensures the production of assets better suited for production environments.
The template engine of .NET 8 now offers a more secure experience through the integration of NuGet's security-related features. These improvements include the prevention of downloading packages from non-HTTPS feeds by default, automatic checks for known vulnerabilities in template packages, and more reliable template package ownership information. These updates significantly enhance the security aspects of the template engine.
In .NET 8, several enhancements have been made to the System.Text.Json serialization and deserialization functionality. These improvements include:
- Performance and reliability enhancements of the source generator when used with ASP.NET Core in native AOT (Ahead-of-Time) apps.
- Support for serializing types with required and init properties, which were previously supported in reflection-based serialization.
- Customization options for handling members that are not present in the JSON payload.
- Serialization support for properties from interface hierarchies, allowing properties from both immediately implemented interfaces and base interfaces to be serialized.
Furthermore, the JsonNamingPolicy class now includes new naming policies for snake_case and kebab-case property name conversions, complementing the existing JsonNamingPolicy.CamelCase policy. The JsonSerializerOptions class now provides more explicit control over when instances are frozen with the MakeReadOnly() method and the IsReadOnly property. Additionally, support for deserializing onto read-only fields or properties without set accessors has been added.
These improvements to the System.Text.Json serialization and deserialization functionality provide developers with greater flexibility, security, and control over their JSON operations in .NET 8.
Core .NET Libraries Enhancements
.NET 8 introduces the TimeProvider class and ITimer interface for easier time abstraction.
These new additions enable developers to mock time in test scenarios and handle Task operations that rely on time progression, such as Task.Delay and Task.WaitAsync. The time abstraction supports essential time operations, including retrieving local and UTC time, obtaining a timestamp for measuring performance, and creating a timer. This makes testing time-dependent code much more manageable and efficient.
To improve UTF8 support, the IUtf8SpanFormattable interface was added in.NET 8. Unlike ISpanFormattable, this interface targets UTF8 and Span<byte> rather than UTF16 and Span<char>. All primitive types, as well as some other types, now implement IUtf8SpanFormattable, allowing them to format to UTF8 directly. Aside from that, new Utf8.TryWrite methods, which provide a UTF8-based counterpart to the existing MemoryExtensions, have also been introduced.
This enables developers to directly format complex expressions into a range of UTF8 bytes.
Methods for working with randomness
.NET 8 includes new methods in the System.Random and System.Security.Cryptography.RandomNumberGenerator types for working with randomness. The GetItems<T>() method allows developers to randomly choose a specified number of items from an input set. The Shuffle<T>() method helps to randomize the order of a span, which is particularly useful in machine learning applications for reducing training bias. These new methods significantly improve the ease and practicality of handling random operations in .NET.
.NET 8 introduces several new performance-based types. The System.Collections.Frozen namespace includes collection types like FrozenDictionary<TKey, TValue> and FrozenSet<T>, which do not allow any changes to keys and values once a collection is created. This enables faster read operations and is particularly useful for collections populated at the beginning of long-lived services. Additionally, the new System.Buffers.IndexOfAnyValues<T> type provides a performance-oriented method for finding the first occurrence of any value in the passed collection. This enhancement makes searching for values substantially faster and more efficient.
These Core .NET Libraries enhancements in .NET 8 provide developers with better tools for time abstraction, UTF8 support, random method handling, and performance-focused types, leading to more efficient and well-optimized applications.
System.Numerics and System.Runtime.Intrinsics Updates
.NET 8 brings a range of improvements and new features to the System.Numerics and System.Runtime.Intrinsics namespaces, such as:
- Enhanced hardware acceleration for Vector256<T>, Matrix3x2, and Matrix4x4 in .NET 8. For instance, Vector256<T> has been reimplemented to internally use 2x Vector128<T> operations wherever possible. This change allows partial acceleration of some functions when Vector128.IsHardwareAccelerated == true, but Vector256.IsHardwareAccelerated == false, such as on Arm64.
- Illustrated with ConstExpected attribute, the hardware intrinsics are now annotated to ensure developers are aware when the underlying hardware expects a constant, and when a non-constant value might unexpectedly impact performance.
- Addition of the Lerp(TSelf, TSelf, TSelf) Lerp API to IFloatingPointIeee754<TSelf> and accordingly to float (Single), double (Double), and Half. This API enables efficient and accurate linear interpolation between two values.
Vector512 and AVX-512
.NET 8 introduces the Vector512<T> and supports Intel Advanced Vector Extensions 512 (AVX-512) instructions, expanding the SIMD (Single Instruction, Multiple Data) support in .NET. The key features of AVX-512 include 512-bit vector operations, an extra 16 SIMD registers, and additional instructions for 128-bit, 256-bit, and 512-bit vectors.
If your hardware supports this functionality, Vector512.IsHardwareAccelerated will report true. Furthermore, .NET 8 adds several platform-specific classes under the System.Runtime.Intrinsics.X86 namespace, such as Avx512F, Avx512BW, Avx512CD, Avx512DQ, and Avx512Vbmi.
These new functionalities allow the JIT to implicitly utilize the additional registers and instructions when working with Vector128<T> or Vector256<T>. The base class library internally adopts these hardware intrinsics for operations exposed by Span<T> and ReadOnlySpan<T>, as well as many math APIs for primitive types.
These updates in the System.Numerics and System.Runtime.Intrinsics namespaces enable developers to harness better-optimized math and vector operations, providing improved performance in various .NET applications.
The System.ComponentModel.DataAnnotations namespace includes new data validation attributes intended for validation scenarios in cloud-native services. Unlike the pre-existing DataAnnotations validators, which are geared towards typical UI data-entry validation, such as fields on a form, the new attributes are designed to validate non-user-entry data, like configuration options. In addition to the new attributes, enhancements have been made to existing validation attributes like RangeAttribute and RequiredAttribute by adding new properties.
New APIs - Description:
- RequiredAttribute.DisallowAllDefaultValues: Validates that structs do not equal their default values.
- RangeAttribute.MinimumIsExclusive and RangeAttribute.MaximumIsExclusive: Specifies whether bounds are included in the allowable range.
- System.ComponentModel.DataAnnotations.LengthAttribute: Specifies both lower and upper bounds for strings or collections. For example, [Length(10, 20)] requires at least 10 elements and at most 20 elements in a collection.
- System.ComponentModel.DataAnnotations.Base64StringAttribute: Validates that a string is a valid Base64 representation.
- System.ComponentModel.DataAnnotations.AllowedValuesAttribute and System.ComponentModel.DataAnnotations.DeniedValuesAttribute: Specify allow lists and deny lists, respectively. For example, [AllowedValues("apple", "banana", "mango")].
These data validation enhancements in .NET 8 provide developers with a comprehensive suite of validation attributes, making it easier to ensure data integrity and correctness in cloud-native services and other non-user-entry data scenarios.
The newest update introduces a new capability to adjust memory limits on the fly. This feature is particularly beneficial in cloud-service scenarios, where demand fluctuates over time. To maintain cost-effectiveness, services should scale resource consumption up and down based on demand variations. When services detect a decrease in demand, they can scale down resource consumption by reducing the memory limit. However, in previous .NET versions, this would fail because the GC could allocate more memory than the new limit. With the introduction of the _RefreshMemoryLimit API in .NET 8, developers can now update the GC with the new memory limit dynamically.
It's essential to be aware of some limitations:
- The _RefreshMemoryLimit API is private currently, so it needs to be called through private reflection.
- On 32-bit platforms (e.g., Windows x86 and Linux ARM), .NET cannot establish a new heap hard limit if there isn't already one
- The API might return a non-zero status code if the scale-down attempt is too aggressive and limits the GC's ability to operate efficiently. In such cases, consider calling GC.Collect(2, GCCollectionMode.Aggressive) to shrink current memory usage before trying again
- If the memory limit is scaled up beyond the size the GC believes the process can handle during startup, the _RefreshMemoryLimit call will succeed, but it won't be able to use more memory than the perceived limit.
These garbage collection improvements in .NET 8 make resource allocation and management in cloud-service scenarios more efficient and flexible, allowing developers to build cost-effective and readily adaptable services.
Reflection adds support for function pointers, a feature that was introduced in .NET 5 but lacked reflection support at that time. When using typeof or reflection on a function pointer (e.g., typeof(delegate*<void()>) or FieldInfo.FieldType), previous .NET versions would return an IntPtr. However, in .NET 8, a System.Type object is returned instead. This type provides access to function pointer metadata, including calling conventions, return type, and parameters.
Note that function pointer instances, which represent physical addresses to functions, still use IntPtr as a representation. The change affects only the reflection type.
These reflection updates in .NET 8 provide significant enhancements and better support for function pointers, streamlining code analysis and increasing the versatility of reflection capabilities.
Native AOT Support
Native AOT (Ahead-of-Time) support was first introduced in .NET 7, enabling developers to publish their applications as fully self-contained versions without the need for a runtime, as everything is bundled into a single file. The .NET 8 Preview 4 improves upon native AOT publishing in several ways:
- Adds support for x64 and Arm64 architectures on macOS, expanding the availability of Native AOT on multiple platforms.
- Reduces Native AOT app sizes on Linux by up to 50%. For example, a "Hello World" app published as Native AOT with the entire .NET runtime sees a significant reduction in size when comparing .NET 7 and .NET 8.
- Introduces an option to specify an optimization preference: size or speed. By default, the compiler generates code focused on speed while also being mindful of the application size. However, the OptimizationPreference property allows developers to optimize specifically for one of the two preferences. For more information, see Optimize AOT deployments.
Console App Template
.NET 8 enhances the default console app template to include native AOT support out-of-the-box. To create a project configured for AOT compilation, simply run dotnet new console --aot. The project configuration added by --aot has three main effects:
- Generates a native self-contained executable with native AOT when you publish the project, using either dotnet publish or Visual Studio.
- Enables compatibility analyzers for trimming, AOT, and single file, which check for potentially problematic areas in your project.
- Offers debug-time emulation of AOT, providing a similar experience during project debugging without AOT compilation, and helping minimize surprises when publishing the project with AOT.
These improvements to .NET 8 Native AOT publishing allow developers to create highly optimized and efficient applications that require minimal runtime overhead and can be easily deployed across multiple platforms.
In .NET 8, several breaking changes have been introduced across various components such as ASP.NET Core, Core .NET libraries, Cryptography, Deployment, Extensions, Globalization, Reflection, SDK, Serialization, and Windows Forms. Some of these breaking changes include:
1. In ASP.NET Core, ConcurrencyLimiterMiddleware is marked as obsolete starting from Preview 4.
2. Core .NET libraries have several breaking changes, such as behavioral changes in Activity operation name when null and FileStream writes when a pipe is closed.
3. In the Cryptography component, AesGcm authentication tag size on macOS has a behavioral change, and RSA.EncryptValue and RSA.DecryptValue are marked as obsolete.
4. For Deployment, the StripSymbols property now defaults to true, leading to a behavioral change in Preview 4.
5. In Extensions, ActivatorUtilities.CreateInstance behaves consistently, and ConfigurationBinder now throws for mismatched values, resulting in behavioral changes.
6. In Globalization, date and time converters now honor the culture argument, and the default value for TwoDigitYearMax has been changed to 2049.
Of course, these are not all changes that the .NET have introduced, and possibly the current list could be longer in the future.
For the full list, please refer to this page.
In conclusion, .NET 8 brings a wealth of enhancements and new features designed to improve the developer experience significantly. These updates span a wide range of functions, such as SDK changes, serialization improvements, Core .NET libraries enhancements, and native AOT support. As a result, developers can create highly optimized and efficient applications with the latest advancements in performance, security, and flexibility. Moreover, .NET 8's long-term support provides a stable platform with ongoing support for the coming years.
It is crucial for senior developers to familiarize themselves with these advancements in .NET 8 and incorporate them into their workflows to stay ahead in the ever-evolving world of software development. Staying up-to-date with the latest updates and breaking changes is important for several reasons:
1. Performance improvements: Regular updates often bring optimizations and fine-tuning that increase the performance and efficiency of your applications. Staying up-to-date with these changes ensures that you can leverage these optimizations in your projects.
2. Security enhancements: Updates often introduce crucial security fixes and enhancements that protect applications from potential vulnerabilities and attacks. Staying current with the latest developments ensures that your applications are as secure as possible.
3. Compatibility: Keeping up with the latest updates fosters compatibility with other software components, libraries, and tools. This enables easier integration, simplifies collaboration, and facilitates the development of more robust and versatile applications.
4. Innovation: Staying updated enables you to utilize the newest features and tools that make your work more efficient or offer new possibilities for your applications.
5. Maintenance and support: Updated software typically receives better support and maintenance from the community and the software developers. Regularly updating your software ensures that you can take advantage of available support resources and knowledge bases.
6. Future-proofing: Embracing updates prepares you for the future by making it easier to transition to upcoming versions and adapt to the continually changing software landscape.
Carefully evaluating and addressing breaking changes is also essential as they may involve modifications that affect your applications' behavior, performance, or compatibility. By staying informed about breaking changes and proactively addressing them, developers can minimize disruption and maintain high-quality, stable applications.
In conclusion, developers must keep up with the newest developments in.NET 8 to future-proof their applications. This includes learning about the many improvements, new features, and breaking changes included in this version.