What's New in .NET 8.0?

What's New in .NET 8.0?

.NET 8 brings thousands of performance improvements across the entire stack

Last updated 11/17/2023 4:03 PM
葡萄城技术团队
21 min read
Category
.NET
Tags
.NET C# Technology Updates

1. Performance Improvements

.NET 8 brings thousands of performance improvements across the entire stack, including .NET MAUI and ARM64. A new code generator called Dynamic Profile-Guided Optimization (PGO) is enabled by default, which optimizes code based on actual usage and can improve application performance by up to 20%. The now-supported AVX-512 instruction set enables parallel operations on 512-bit data vectors, meaning more data can be processed in less time. Primitive types (numeric and others) now implement new formattable and parsable interfaces, allowing them to be directly formatted and parsed to and from UTF-8 without any transcoding overhead. img

2. .NET Aspire

.NET Aspire is a stack for building resilient, observable, and configurable cloud-native applications with .NET. It includes a set of curated components enhanced for cloud-native scenarios, with telemetry, resilience, configuration, and health checks enabled by default. Combined with a rich yet simple local developer experience, .NET Aspire makes it easy to discover, acquire, and configure essential dependencies for cloud-native applications on Day 1 and Day 100.

Click here to see the preview version of .NET Aspire. (Editor's note: link missing, will be found and added later) img

3. .NET 8 Container Enhancements – More Secure, Compact, and Efficient

It's easier and safer than ever to package applications with containers using .NET. Every .NET image includes a non-root user, enabling more secure containers with a single configuration line. The .NET SDK tool can publish container images without a Dockerfile and is non-root by default. Smaller .NET base images mean faster deployment of containerized applications – including new experimental variants of our images that provide truly minimal application sizes for Native AOT. Opt for further security hardening with the new Chiseled Ubuntu image variants to reduce the attack surface even more. Build applications and container images for any architecture using Dockerfiles or SDK tools.

img

4. Native AOT – A Journey Towards Higher Density Sustainable Computing

No need to wait for the JIT (Just-In-Time) compiler to compile code at runtime. No need to deploy the JIT compiler and IL code. AOT applications deploy only the code required by the application. Applications can now run in restricted environments where the JIT compiler is not allowed.

img

5. AI – Integrate AI into Your .NET Applications

Generative AI and Large Language Models are transforming the AI landscape, enabling developers to create unique AI experiences in their applications. .NET 8 makes it easy to leverage AI through first-class, out-of-the-box AI capabilities in the .NET SDK and seamless integration with a variety of tools.

.NET 8 brings several enhancements to the library to improve its compatibility with generative AI workloads, such as integrating Tensor Primitives. With the rise of AI applications, new tools and SDKs have emerged. We have collaborated with numerous internal and external partners, such as Azure OpenAI, Azure Cognitive Search, Milvus, Qdrant, and Microsoft Teams, to ensure that .NET developers can easily access a wide range of AI models, services, and platforms through their respective SDKs. Additionally, the open-source Semantic Kernel System.Numerics SDK simplifies the integration of these AI components with new and existing applications to help you deliver innovative user experiences.

Various samples and reference templates showcasing patterns and practices are now available so developers can get started easily:

img

6. Blazor – Build Full-Stack Web Applications with .NET

Blazor in .NET 8 can use both server and client for all your web UI needs. It's full-stack web UI! With multiple new enhancements focused on optimizing page load times, scalability, and improving user experience, developers can now use Blazor Server and Blazor WebAssembly in the same application, automatically transitioning users from server to client at runtime. Thanks to the new "Jiterpreter"-based runtime and new built-in components, your .NET code runs significantly faster on WebAssembly. As part of enhanced overall authentication, authorization, and identity management in .NET 8, Blazor now supports generating a complete Blazor-based identity UI.

img

7. .NET MAUI – Improved Performance, Reliability, and Developer Experience

.NET MAUI provides a single project system and a single codebase to build WinUI, Mac Catalyst, iOS, and Android applications. Native AOT (experimental) now supports iOS-like platforms. A new Visual Studio Code extension for .NET MAUI provides the tools you need to develop cross-platform .NET mobile and desktop applications. Xcode 15 and Android API 34 are now supported, allowing you to target the latest versions of iOS and Android. Numerous quality improvements have been made in performance, controls and UI elements, and platform-specific behaviors, such as better click handling, keyboard listeners, etc., added for desktop interactions.

img

8. C# 12 Features – Simplified Syntax for Developer Productivity

C# 12 makes your coding experience more efficient and enjoyable. You can now create primary constructors in any class and struct using a simple and elegant syntax. No more boilerplate code to initialize your fields and properties. You'll be happy to create arrays, spans, and other collection types with a concise and expressive syntax. Use new default values for parameters in lambda expressions. No more overloads or null checks to handle optional parameters. You can even alias any type using a using alias directive, not just named types!

8.1. Collection Expressions

Before C# 12, creating collections required different syntaxes for different scenarios. Initializing List<int>, int[], and Span<int> required different syntaxes. Here are several ways to create collections:

int[] x1 = new int[] { 1, 2, 3, 4 };
int[] x2 = Array.Empty<int>();
WriteByteArray(new[] { (byte)1, (byte)2, (byte)3 });
List<int> x4 = new() { 1, 2, 3, 4 };
Span<DateTime> dates = stackalloc DateTime[] { GetDate(0), GetDate(1) };
WriteByteSpan(stackalloc[] { (byte)1, (byte)2, (byte)3 });

8.2. Primary Constructors on Any Class or Struct

C# 12 extends primary constructors to all classes and structs, not just records. Primary constructors allow you to define constructor parameters when declaring a class:

public class BankAccount(string accountID, string owner)
{
    public string AccountID { get; } = accountID;
    public string Owner { get; } = owner;

    public override string ToString() => $"Account ID: {AccountID}, Owner: {Owner}";
}

The most common uses of primary constructor parameters are:

  • As arguments to a base() constructor call.
  • To initialize member fields or properties.
  • To reference constructor parameters in instance members.
  • To eliminate boilerplate in dependency injection.

8.3. Alias Any Type

Aliasing types is a convenient way to remove complex type signatures from your code. Starting with C# 12, other types are valid in using alias directives. For example, these aliases were invalid in earlier versions of C#:

using intArray = int[]; // Array types.
using Point = (int x, int y);  // Tuple type
using unsafe ArrayPtr = int*;  // Pointer type (requires "unsafe")

8.4. Default Lambda Parameters

Starting with C# 12, you can declare default parameters in lambda expressions:

var IncrementBy = (int source, int increment = 1) => source + increment;

Console.WriteLine(IncrementBy(5)); // 6
Console.WriteLine(IncrementBy(5, 2)); // 7

8.5. Inline Arrays

The runtime team and other library authors use inline arrays to improve application performance. Inline arrays enable developers to create fixed-size struct type arrays. Structs with inline buffers should provide performance characteristics similar to unsafe fixed-size buffers. You may not declare your own inline arrays, but you will transparently use them when they are exposed from runtime APIs as System.Span or System.ReadOnlySpan objects.

[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
    private int _element0;
}

They are used similarly to any other array:

var buffer = new Buffer();
for (int i = 0; i < 10; i++)
{
    buffer[i] = i;
}

foreach (var i in buffer)
{
    Console.WriteLine(i);
}

The difference is that the compiler can leverage known information about inline arrays. You might use inline arrays like any other array. For details on how to declare inline arrays, see the language reference on struct types.

9. Reflection Improvements

Function pointers were introduced in .NET 5, but corresponding support for reflection was not added at that time. Using typeof or reflection on function pointers (e.g., using typeof(delegate*<void>()) or FieldInfo.FieldType) returned IntPtr. Starting with .NET 8, System.Type objects are returned instead. This type provides access to function pointer metadata, including calling convention, return type, and parameters.

The new functionality is currently implemented only in the CoreCLR runtime and MetadataLoadContext. New APIs have been added to System.Type (such as IsFunctionPointer) and System.Reflection.PropertyInfo, System.Reflection.FieldInfo, and System.Reflection.ParameterInfo. The following code demonstrates using some of the new APIs for reflection.

// Sample class that contains a function pointer field.
public unsafe class UClass
{
    public delegate* unmanaged[Cdecl, SuppressGCTransition]<in int, void> _fp;
}

// ...

FieldInfo fieldInfo = typeof(UClass).GetField(nameof(UClass._fp));

// Obtain the function pointer type from a field.
Type fpType = fieldInfo.FieldType;

// New methods to determine if a type is a function pointer.
Console.WriteLine($"IsFunctionPointer: {fpType.IsFunctionPointer}");
Console.WriteLine($"IsUnmanagedFunctionPointer: {fpType.IsUnmanagedFunctionPointer}");

// New methods to obtain the return and parameter types.
Console.WriteLine($"Return type: {fpType.GetFunctionPointerReturnType()}");

foreach (Type parameterType in fpType.GetFunctionPointerParameterTypes())
{
    Console.WriteLine($"Parameter type: {parameterType}");
}

// Access to custom modifiers and calling conventions requires a "modified type".
Type modifiedType = fieldInfo.GetModifiedFieldType();

// A modified type forwards most members to its underlying type.
Type normalType = modifiedType.UnderlyingSystemType;

// New method to obtain the calling conventions.
foreach (Type callConv in modifiedType.GetFunctionPointerCallingConventions())
{
    Console.WriteLine($"Calling convention: {callConv}");
}

// New method to obtain the custom modifiers.
foreach (Type modreq in modifiedType.GetFunctionPointerParameterTypes()[0].GetRequiredCustomModifiers())
{
    Console.WriteLine($"Required modifier for first parameter: {modreq}");
}

Output:

IsFunctionPointer: True
IsUnmanagedFunctionPointer: True
Return type: System.Void
Parameter type: System.Int32&
Calling convention: System.Runtime.CompilerServices.CallConvSuppressGCTransition
Calling convention: System.Runtime.CompilerServices.CallConvCdecl
Required modifier for first parameter: System.Runtime.InteropServices.InAttribute

10. Configuration Binding Source Generator

.NET 8 introduces a source generator that provides AOT and trimming-compatible configuration in ASP.NET Core. The generator is an alternative to the existing reflection-based implementation.

The source generator probes Configure(TOptions) (https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.options.configureoptions-1.configure#microsoft-extensions-options-configureoptions-1-configure(-0)), Bind, and Get (https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.configurationbinder.get) calls to retrieve type information. When the generator is enabled in a project, the compiler implicitly selects the generated methods instead of the pre-existing reflection-based framework implementation.

No source code changes are required to use the generator. It is enabled by default in AOT web applications. For other project types, the source generator is off by default, but you can opt in by setting the EnableConfigurationBindingGenerator property to true in your project file:

<PropertyGroup>
    <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>

The following code demonstrates an example of invoking the binder:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
IConfigurationSection section = builder.Configuration.GetSection("MyOptions");

// !! Configure call - to be replaced with source-gen'd implementation
builder.Services.Configure<MyOptions>(section);

// !! Get call - to be replaced with source-gen'd implementation
MyOptions options0 = section.Get<MyOptions>();

// !! Bind call - to be replaced with source-gen'd implementation
MyOptions options1 = new MyOptions();
section.Bind(options1);

WebApplication app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();

public class MyOptions
{
    public int A { get; set; }
    public string S { get; set; }
    public byte[] Data { get; set; }
    public Dictionary<string, string> Values { get; set; }
    public List<MyClass> Values2 { get; set; }
}

public class MyClass
{
    public int SomethingElse { get; set; }
}

11. AOT Compilation for Android Apps

To reduce app size, .NET and .NET MAUI apps targeting Android use a profiled Ahead-of-Time (AOT) compilation mode when built in Release mode. Compared to regular AOT compilation, profiled AOT affects fewer methods. .NET 8 introduces the <AndroidStripILAfterAOT> property, which you can use to further AOT-compile Android apps, resulting in even smaller app sizes.

<PropertyGroup>
  <AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
</PropertyGroup>

By default, setting AndroidStripILAfterAOT to true overrides the default AndroidEnableProfiledAot setting, allowing trimming of (almost) all AOT-compiled methods. You can also combine profiled AOT and IL stripping by explicitly setting both properties to true:

<PropertyGroup>
  <AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
  <AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
</PropertyGroup>

12. Code Analysis

.NET 8 includes several new code analyzers and fixes to help verify that .NET library APIs are used correctly and efficiently. The table below summarizes the new analyzers.

Rule ID Category Description
CA1856 Performance Triggered when the ConstantExpectedAttribute is not correctly applied on a parameter.
CA1857 Performance Triggered when a parameter is annotated with ConstantExpectedAttribute but the provided argument is not a constant.
CA1858 Performance To determine if a string starts with a given prefix, it's better to call String.StartsWith instead of calling String.IndexOf and comparing the result to zero.
CA1859 Performance This rule recommends upgrading specific local variables, fields, properties, method parameters, and method return types from interface or abstract types to concrete types when possible. Using concrete types can produce higher quality code.
CA1860 Performance To determine if a collection type has any elements, it's better to use Length, Count, or IsEmpty instead of calling Enumerable.Any.
CA1861 Performance Constant arrays passed as arguments are not reused on repeated calls, meaning a new array is created each time. To improve performance, consider extracting the array to a static readonly field.
CA1865-CA1867 Performance For single characters, the char overload performs better.
CA2021 Reliability Enumerable.Cast(IEnumerable) and Enumerable.OfType(IEnumerable) require compatible types to function correctly. Widening and user-defined conversions are not supported for generic types.
CA1510-CA1513 Maintainability Throw helpers are simpler and more efficient than if blocks when constructing new exception instances. These four analyzers are created for the following exceptions: ArgumentNullException, ArgumentException, ArgumentOutOfRangeException, and ObjectDisposedException.

13. .NET Core Libraries

13.1. Time Abstraction

The new TimeProvider class and ITimer interface add time abstraction capabilities, allowing you to simulate time in test scenarios. Additionally, you can use time abstractions to simulate Task operations that depend on time progression, such as Task.Delay and Task.WaitAsync. Time abstractions support the following basic time operations:

  • Retrieving local and UTC time
  • Getting timestamps for performance measurement
  • Creating timers

The following code snippet demonstrates some usage examples.

// Get system time.
DateTimeOffset utcNow = TimeProvider.System.GetUtcNow();
DateTimeOffset localNow = TimeProvider.System.GetLocalNow();

// Create a time provider that works with a
// time zone that's different than the local time zone.
private class ZonedTimeProvider : TimeProvider
{
    private TimeZoneInfo _zoneInfo;

    public ZonedTimeProvider(TimeZoneInfo zoneInfo) : base()
    {
        _zoneInfo = zoneInfo ?? TimeZoneInfo.Local;
    }

    public override TimeZoneInfo LocalTimeZone => _zoneInfo;

    public static TimeProvider FromLocalTimeZone(TimeZoneInfo zoneInfo) =>
        new ZonedTimeProvider(zoneInfo);
}

// Create a timer using a time provider.
ITimer timer = timeProvider.CreateTimer(callBack, state, delay, Timeout.InfiniteTimeSpan);

// Measure a period using the system time provider.
long providerTimestamp1 = TimeProvider.System.GetTimestamp();
long providerTimestamp2 = TimeProvider.System.GetTimestamp();

var period = GetElapsedTime(providerTimestamp1, providerTimestamp2);

13.2. UTF8 Improvements

To enable writing a string-like representation of a type to a target span, implement the new IUtf8SpanFormattable interface on the type. This new interface is closely related to ISpanFormattable but targets UTF8 and Span<byte> instead of UTF16 and Span<char>.

IUtf8SpanFormattable is already implemented on all primitive types (and others), and its shared logic is fully consistent whether targeting string, Span<char>, or Span<byte>. It fully supports all formats (including the new "B" binary specifier) and all cultures. This means you can now format directly to UTF8 from Byte, Complex, Char, DateOnly, DateTime, DateTimeOffset, Decimal, Double, Guid, Half, IPAddress, IPNetwork, Int16, Int32, Int64, Int128, IntPtr, NFloat, SByte, Single, Rune, TimeOnly, TimeSpan, UInt16, UInt32, UInt64, UInt128, UIntPtr, and Version.

The new Utf8.TryWrite method provides a UTF8-based counterpart to the existing MemoryExtensions.TryWrite method (based on UTF16). You can use interpolated string syntax to format complex expressions directly into a UTF8 byte span, for example:

static bool FormatHexVersion(
    short major,
    short minor,
    short build,
    short revision,
    Span<byte> utf8Bytes,
    out int bytesWritten) =>
    Utf8.TryWrite(
        utf8Bytes,
        CultureInfo.InvariantCulture,
        $"{major:X4}.{minor:X4}.{build:X4}.{revision:X4}",
        out bytesWritten);

13.3. Cryptography

.NET 8 adds support for the SHA-3 hash primitive. (SHA-3 is currently supported on Linux with OpenSSL 1.1.1 or later and on Windows 11 Build 25324 or later.) APIs where SHA-2 could be used now offer SHA-3 counterparts. For hashing, this includes SHA3_256, SHA3_384, and SHA3_512; for HMAC, this includes HMACSHA3_256, HMACSHA3_384, and HMACSHA3_512; for hashing where the algorithm is configurable, this includes HashAlgorithmName.SHA3_256, HashAlgorithmName.SHA3_384, and HashAlgorithmName.SHA3_512; for RSA OAEP encryption, this includes RSAEncryptionPadding.OaepSHA3_256, RSAEncryptionPadding.OaepSHA3_384, and RSAEncryptionPadding.OaepSHA3_512.

The following example demonstrates using the API, including the SHA3_256.IsSupported property to determine if the platform supports SHA-3.

// Hashing example
if (SHA3_256.IsSupported)
{
    byte[] hash = SHA3_256.HashData(dataToHash);
}
else
{
    // ...
}

// Signing example
if (SHA3_256.IsSupported)
{
     using ECDsa ec = ECDsa.Create(ECCurve.NamedCurves.nistP256);
     byte[] signature = ec.SignData(dataToBeSigned, HashAlgorithmName.SHA3_256);
}
else
{
    // ...
}

13.4. Stream-Based ZipFile Methods

.NET 8 includes new overloads of ZipFile.CreateFromDirectory that allow you to gather all files contained in a directory, compress them, and store the resulting zip file into a provided stream. Similarly, new ZipFile.ExtractToDirectory overloads allow you to provide a stream containing compressed files and extract its contents to the file system. Here are the new overloads:

namespace System.IO.Compression;

public static partial class ZipFile
{
    public static void CreateFromDirectory(string sourceDirectoryName, Stream destination);
    public static void CreateFromDirectory(string sourceDirectoryName, Stream destination, CompressionLevel compressionLevel, bool includeBaseDirectory);
    public static void CreateFromDirectory(string sourceDirectoryName, Stream destination, CompressionLevel compressionLevel, bool includeBaseDirectory, Encoding? entryNameEncoding);

    public static void ExtractToDirectory(Stream source, string destinationDirectoryName) { }
    public static void ExtractToDirectory(Stream source, string destinationDirectoryName, bool overwriteFiles) { }
    public static void ExtractToDirectory(Stream source, string destinationDirectoryName, Encoding? entryNameEncoding) { }
    public static void ExtractToDirectory(Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles) { }
}

GrapeCity Documents for Excel (abbreviated as GcExcel) is a high-performance server-side spreadsheet component based on .NET and .NET Core platforms. It does not require Office, NPOI, or any third-party application software. Use it to display spreadsheet data on the frontend, and batch create, load, edit, print, import/export Excel documents on the server side, providing frontend/backend data synchronization, online filling, server-side batch export and printing for applications you develop.

ActiveReports is a reporting control focused on .NET and .NET Core platforms. With a drag-and-drop report designer, you can quickly design report types such as Excel tables, Word documents, charts, data filtering, data drill-down, precise form printing, etc., fully meeting the development needs of various reports in WinForm, ASP.NET, ASP.NET MVC, and WPF platforms. Additionally, rich APIs allow flexible customization of report creation, loading, and runtime personalization.

Wyn Business Intelligence is a new generation embedded BI product based on GrapeCity's 20+ years of experience in data analysis technology. It aims to provide data analysis functionality that can be deeply integrated with application systems. It can deeply integrate with existing enterprise business systems such as OA, ERP, MES, CRM, etc., analyze data from multiple business systems, perform self-service analysis of business data, real-time analysis and decision-making, and comprehensively enhance enterprise competitiveness.

Spread .NET is a .NET spreadsheet control with functionality and layout highly similar to Excel. It fully meets the needs of table data processing and data visualization development in WinForm, ASP.NET, XAML, and WinRT platforms. Spread .NET supports 462 Excel formulas, provides an embeddable Excel-like designer and fully open APIs, offering .NET developers a more professional choice for building enterprise-level table applications.

References:

https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#networking

Extended Links:

How to Import/Export Excel XLSX in the Frontend Browser Using Blazor Framework

How to Create Flowcharts in .NET Spreadsheet Applications

How to Display Real-Time Data in Frontend Spreadsheets


This article is published by GrapeCity Technology Development Team. For reprint, please indicate the source: GrapeCity Official Website

Keep Exploring

Related Reading

More Articles