What is .NET 5 and why is it so significant?
At Assemblysoft we have been using .NET 5.0 since it was in preview. It’s a great choice for your next app, and a straightforward upgrade from earlier .NET Core versions. We enjoy using it, on desktop, laptop, and in the cloud.
.Net 5 is the future of the .Net platform.
.NET 5.0 is the beginning of the .NET unification journey. It will mean Xamarin developers can use the unified .NET platform.
What is .Net 5?
.NET 5 is the next version of .NET Core and the future of the .NET platform. With .NET 5 you have everything you need to build rich, interactive front end web UI and powerful backend services.
It's a major release.
New Features (TLDR)
ASP.NET Core in .NET 5 is loaded with lots of great new features and improvements. Here’s a sampling:
- MVC model binding improvements, including support for C# 9 record types
- Blazor Server & Blazor WebAssembly support and improvements
- Built-in OpenAPI and Swagger UI support for Web APIs
- SignalR Hub filters and parallel Hub invocations
- Azure AD authentication with MIcrosoft.Identity.Web
- Auto browser refresh with
dotnet watch
- HTTP/2 and gRPC performance improvements
What’s Getting Retired?
Before we get into details and start talking about all the new things that will be coming to .NET 5, let us first cover all the important components that Microsoft is cutting from the platform.
Since most of us prefer to use different tools and environments when it comes to developing applications, it’s best that we first talk about the elements that will no longer be part of the .NET Core.
For a lot of devs out there, this will be a total game-changer so let’s make sure that we cover all the important changes:
If you would like some assistance with .NET MAUI | Azure | Azure DevOps Services | Blazor Development then please get in touch, we would be glad to help.
Web Forms
ASP .NET Web Forms are officially retiring with .NET 4.8. Microsoft has confirmed that ASP .NET Web Forms won’t be coming to .NET 5 and has recommended that devs switch to Blazor. Sure, there are also other alternatives to pursue like Angular, React, and VueJS.
If you are currently using ASP.NET MVC as a full-stack web app, you can continue to do so. ASP.NET Core MVC or the new Razor Pages are still acceptable solutions for quickly building web form applications without views and controllers.
However, if you are building complex apps for enterprise-level businesses, then it’s highly advisable to forget about ASP.NET Core MVC and consider migrating to Blazor, Angular, or React.
for more information on blazor development, please see our article on developing applications with blazor.
https://services.assemblysoft.com/blog/should-i-use-blazor-for-my-next-application/
Microsoft has provided instructions on how to migrate your entire work to Blazor, so there’s nothing standing in your way now if you’re looking to update your stack web app.
WCF
Microsoft said that WCF is going to share the fate of Web Forms as far as .NET 5 is concerned. Even though there has been a lot of heated discussions whether to bring the WCF to .NET Core or not, Microsoft has eventually decided to leave in the past because their initial estimation for adapting WCF to .NET Core was 3 years ago.
As a substitute, Microsoft is promoting gRPC. The company feels that this open-source, high-performance framework can run on any RPC environment.
WWF (Windows Workflow Foundation)
WWF is also getting cut. If you still rely on WWF to create applications that execute ordered business processes, like hiring candidates for specific positions, Microsoft is advising you to look at gRPC.
If you are seeking help working out the roadmap of how to continue to support your WWF processes and applications, talk to us. Here at Assemblysoft we have developed numerous applications using Windows Workflow Foundation. We can help you continue to support and manage your WWF wokflows in .NET5 as well has migrate to gRPC.
Download
You can download .NET 5.0, for Windows, macOS, and Linux, for x86, x64, Arm32, Arm64.
- Installers and binaries
- Container images
- Linux packages
- Release notes
- Known issues
- GitHub issue tracker
- .NET 5.0 Contributors
What tooling do I need?
For Visual Studio users, you need Visual Studio 16.8 or later to use .NET 5.0 on Windows and the latest version of Visual Studio for Mac) on macOS. The C# extension for Visual Studio Code already supports .NET 5.0 and C# 9.
.NET 5.0 Highlights
There are many important improvements in .NET 5.0:
- .NET 5.0 is already battle-tested by being hosted for months at dot.net and Bing.com (version).
- Performance is greatly improved across many components and is described in detail at Performance Improvements in .NET 5.0, Arm64 Performance in .NET 5.0, and gRPC.
- C# 9 and F# 5 offer new language improvements such as top-level programs and records for C# 9, while F# 5 offers interactive programming and a performance boost for functional programming on .NET.
- .NET libraries have enhanced performance for Json serialization, regular expressions, and HTTP (HTTP 1.1, HTTP/2). They are also are now completely annotated for nullability.
- P95 latency has dropped due to refinements in the GC, tiered compilation, and other areas.
- Application deployment options are better, with ClickOnce client app publishing, single-file apps, reduced container image size, and the addition of Server Core container images.
- Platform scope expanded with Windows Arm64 and WebAssembly.
there are many samples for the .NET 5.0 preview posts. You might want to take a look at .NET 5.0 Examples to learn more about new C# 9 and libraries features.
Platform and Microsoft Support
.NET 5.0 has a nearly identical platform support matrix as .NET Core 3.1, for Windows, macOS, and Linux. If you are using .NET Core 3.1 on a supported operating system, you should be able to adopt .NET 5.0 on that same operating system version for the most part. The most significant addition for .NET 5.0 is Windows Arm64.
.NET 5.0 is a current release. That means that it will be supported for three months after .NET 6.0 is released. As a result, we expect to support .NET 5.0 through the middle of February 2022. .NET 6.0 will be an LTS release and will be supported for three years, just like .NET Core 3.1.
Unified platform vision
Last year, we heard of the vision of a unified .NET stack and ecosystem. The value to you is that you will be able to use a single set of APIs, languages, and tools to target a broad set of application types, including mobile, cloud, desktop, and IoT. You might realize that you can already target a broad set of platforms with .NET today, however, the tools and APIs are not always the same across Web and Mobile, for example, or released at the same time.
As part of .NET 5.0 and 6.0, there is a unifying .NET into a single product experience, while enabling you to pick just the parts of the .NET platform that you want to use. If you want to target Mobile and not WebAssembly, you don’t need to download the WebAssembly tools, and vice versa. Same with ASP.NET Core and WPF. You’ll also have a much easier way to acquire all the .NET tools and build and runtime packs that you need from the command line. We’re enabling a package manager experience (including using existing package managers) for .NET platform components. That will be great for many scenarios. Quick construction of a development environment and CI/CD will probably be the biggest beneficiaries.
Code Repository Consolidation
The first step towards this vision was consolidating .NET repos, including a large subset of Mono. Having one repo for the runtime and libraries for .NET is a precondition to delivering the same product everywhere. It also helps with making broad changes that affect runtime and libraries, where there were previously repo boundaries. Some people were worried that a large repo would be harder to manage. That hasn’t proven to be the case.
In the .NET 5.0 release, Blazor is the best example of taking advantage of repo consolidation and .NET unification. The runtime and libraries for Blazor WebAssembly are now built from the consolidated dotnet/runtime repo. That means Blazor WebAssembly and Blazor on the server use the exact same code for List<T>
, for example. That wasn’t the case for Blazor prior to .NET 5.0.
What about .NET (Classic Framework)
The .NET Framework remains a supported Microsoft product and will continue to be supported with each new version of Windows. The announcement last year highlighted Microsoft stopped adding new features to .NET Framework and finished adding .NET Framework APIs to .NET Core. That means that now is a great time to consider moving your .NET Framework apps to .NET Core. For .NET Framework client developers, Windows Forms and WPF are supported with .NET 5.0.
Porting from .NET Framework is straightforward. For .NET Framework server developers, you need to adopt ASP.NET Core to use .NET 5.0. For Web Forms developers, we believe that Blazor provides a similar developer experience with an efficient and much more modern implementation. WCF server and Workflow users can look to community projects that are supporting those frameworks. The porting from .NET Framework to .NET Core doc is a good place to start. That all said, keeping your app on .NET Framework is a fine approach if you are happy with your experience.
So what's new?
Let’s switch to looking at what’s new in the 5.0 release.
Languages
C# 9 and F# 5 are part of the .NET 5.0 release and included in the .NET 5.0 SDK. Visual Basic is also included in the 5.0 SDK. It does not include language changes, but has improvements to support the Visual Basic Application Framework on .NET Core.
C# Source Generators are an important new C# compiler feature. They are not technically part of C# 9 since it doesn’t have any language syntax. See New C# Source Generator Samples to help you get started using this new feature. We expect to make more use of source generators within the .NET product in .NET 6.0 and beyond.
As a way to try out the new release ourselves, a few of us decided to update the dotnet/iot repo to use new C# 9 syntax and target .NET 5.0. The changes resulted in removing >2k lines of code, just by adopting new syntax. It uses top-level programs, records, patterns, and switch expressions. It has also been updated to take advantage of the complete set of nullable annotations in .NET libraries. We also updated the .NET IoT docs. We’ll take a look at few examples from that repo to explore C# 9.
Top-level programs
The led-blink program is a nice compact top-level program example.
using System;
using System.Device.Gpio;
using System.Threading;
var pin = 18;
var lightTime = 1000;
var dimTime = 200;
Console.WriteLine($"Let's blink an LED!");
using GpioController controller = new ();
controller.OpenPin(pin, PinMode.Output);
Console.WriteLine($"GPIO pin enabled for use: {pin}");
// turn LED on and off
while (true)
{
Console.WriteLine($"Light for {lightTime}ms");
controller.Write(pin, PinValue.High);
Thread.Sleep(lightTime);
Console.WriteLine($"Dim for {dimTime}ms");
controller.Write(pin, PinValue.Low);
Thread.Sleep(dimTime);
}
You can also see the use of target-typed new
, with the assignment to the controller
variable. The GpioController
type is only defined on the left-hand side of the assignment. The type is inferred on the right-hand side. This new syntax is an alternative to var
, which has the type only showing on the right-hand side of the assignment and is inferred on the left-hand side with the var
keyword.
Top-level programs can also grow in complexity, by defining methods and taking advantage of types defined in the same or other files. The CharacterLcd sample demonstrates some of those capabilities.
Logical and property patterns
C# 9 includes support for new patterns. You can see an example of a logical pattern in the following code from the CCS811 Gas sensor.
var threshChoice = Console.ReadKey();
Console.WriteLine();
if (threshChoice.KeyChar is 'Y' or 'y')
{
TestThresholdAndInterrupt(ccs811);
}
Another new pattern is property patterns. You can see several properties checks in my Mycroft information access 6.0 sample. The following code is taken from the PN532 RFID and NFC reader sample.
if (pollingType is null or { Length: >15 }){ return null;}
This code tests if pollingType
(which is typed as byte[]?
) is null or contains >15 bytes.
I want to show you two more patterns. The first is a logical pattern in Mcp25xxx CAN bus.
public static byte GetRxBufferNumber(Address address) => address switch{ >= Address.RxB0D0 and <= Address.RxB0D7 => 0, >= Address.RxB1D0 and <= Address.RxB1D7 => 1, _ => throw new ArgumentException(nameof(address), $"Invalid address value {address}."),};
The second is a logical pattern in Piezo Buzzer Controller.
if (element is not NoteElement noteElement){ // In case it's a pause element we have only just wait desired time. Thread.Sleep(durationInMilliseconds);}else{ // In case it's a note element we play it. var frequency = GetFrequency(noteElement.Note, noteElement.Octave); _buzzer.PlayTone(frequency, (int)(durationInMilliseconds * 0.7)); Thread.Sleep((int)(durationInMilliseconds * 0.3));}
Records
C# 9 includes a new type of class called a record. It has a number of benefits compared to regular classes, half of which relate to more terse syntax. The following record is taken from the Bh1745 RGB Sensor binding.
public record ChannelCompensationMultipliers(double Red, double Green, double Blue, double Clear);
It is then used a little later in the same file, with familiar syntax:
ChannelCompensationMultipliers = new (2.2, 1.0, 1.8, 10.0);
Nullability annotation improvements
The .NET libraries are now completely annotated for nullability. That means if you enable nullability, you’ll get more type information from the platform to direct your use of the feature. At present, the .NET docs have not been fully annotated. For example, String.IsNullOrEmpty(string)
should be annotated to take a string?
, while String.Split(Char[]) has an annotation of char[]?
. We hope that will get fixed soon. Complete information is available at source.dot.net and via F12 metadata lookups in Visual Studio.
The System.Device.Gpio and Iot.Device.Bindings packages (version 1.1.0 for both) have also been annotated as part of this release, using the updated .NET 5.0 annotations. Those libraries are both multi-targeted, however, we use the 5.0 view to produce annotations for all targets.
Note: Existing .NET Core 3.1 code might generate new diagnostics (if you have nullability enabled) when you retarget it to .NET 5.0, since there are new annotations.
We’ve also added new annotation types. It’s common for large classes to instantiate object members in helper methods called from a constructor. The C# compiler can’t follow the flow of calls to the object assignment. It will think that the member is null when exiting the constructor and will warn with CS8618
. The MemberNotNull attribute resolves this problem. You apply the attribute to the helper method. The compiler will then see that you set this value and realize that the method is called from a constructor. MemberNotNullWhen is similar.
You can see an example of MemberNotNull
in the BMxx80 temperature sensors with the following code.
[MemberNotNull(nameof(_calibrationData))]
private void ReadCalibrationData()
{
switch (this)
{
case Bme280 _:
_calibrationData = new Bme280CalibrationData();
_controlRegister = (byte)Bmx280Register.CTRL_MEAS;
break;
case Bmp280 _:
_calibrationData = new Bmp280CalibrationData();
_controlRegister = (byte)Bmx280Register.CTRL_MEAS;
break;
case Bme680 _:
_calibrationData = new Bme680CalibrationData();
_controlRegister = (byte)Bme680Register.CTRL_MEAS;
break;
default:
throw new Exception("Bmxx80 device not correctly configured. Could not find calibraton data.");
}
_calibrationData.ReadFromDevice(this);
}
The actual code uses conditional compilation. That’s because the project is multi-targeted, and this attribute is only supported with .NET 5.0+ . The use of the attribute enables skipping runtime checks (in the constructor) that would otherwise be needed to satisfy nullability requirements, as is the case for earlier .NET versions.
Tools
We’ve improved the Windows Forms designer, changed the way that target frameworks work for .NET 5.0 and beyond, changed the way that WinRT is supported, and made other improvements.
Windows Forms designer
The Windows Forms designer (for .NET Core 3.1 and .NET 5.0) has been updated in Visual Studio 16.8, and now supports all Windows Forms controls. It also supports the Telerik UI for WinForms controls. The designer includes all the designer functionality you would expect, including: drag-and-drop, selection, move and resize, cut/copy/paste/delete of controls, integration with the Properties Window, events generation and more. Data binding and support for a broader set of third party controls is coming soon.
Learn more in the Windows Forms Designer for .NET Core Released post.
.NET 5.0 Target Framework
We have changed the approach we use for target frameworks with .NET 5.0. The following project file demonstrate the new .NET 5.0 target framework.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
The new net5.0
form is more compact and intuitive than the netcoreapp3.1
style we’ve used until this point. In addition, we are extending the target framework to describe operating system dependencies. This change is motivated by our vision to enable targeting iOS and Android with Xamarin in .NET 6.0.
Windows desktop APIs (including Windows Forms, WPF, and WinRT) will only be available when targeting net5.0-windows
. You can specify an operating system version, like net5.0-windows7
or net5.0-windows10.0.17763.0
( for Windows October 2018 Update). You need to target a Windows 10 version if you want to use WinRT APIs.
Cross-platform scenarios can be a bit more challenging, when using the new net5.0-windows
TFM. System.Device.Gpio demonstrates a pattern for managing the Windows target-framework if you want to avoid building for Windows or avoid pulling Windows runtime packages on Linux, for example.
Summary of changes:
net5.0
is the new Target Framework Moniker (TFM) for .NET 5.0.net5.0
combines and replacesnetcoreapp
andnetstandard
TFMs.net5.0
supports .NET Framework compatibility modenet5.0-windows
will be used to expose Windows-specific functionality, including Windows Forms, WPF and WinRT APIs.- .NET 6.0 will use the same approach, with
net6.0
, and will addnet6.0-ios
andnet6.0-android
. - The OS-specific TFMs can include OS version numbers, like
net6.0-ios14
. - Portable APIs, like ASP.NET Core will be usable with
net5.0
. The same will be true of Xamarin forms withnet6.0
.
The templates in Visual Studio 16.8 still target .NET Core 3.1, for console, WPF and Windows Forms apps. The ASP.NET templates have been updated to support .NET 5.0. We will update the templates in Visual Studio 16.9 for the remaining templates.
WinRT Interop (Breaking Change)
On the topic of targeting Windows APIs, we have moved to a new model for supporting WinRT APIs as part of .NET 5.0. This includes calling APIs (in either direction; CLR <==> WinRT), marshaling of data between the two type systems, and unification of types that are intended to be treated the same across the type system or ABI boundary (i.e. “projected types”; IEnumerable<T>
and IIterable<T>
are examples).
The existing WinRT interop system has been removed from the .NET runtime as part of .NET 5.0. This is a breaking change. That means that apps and libraries using WinRT with .NET Core 3.x will need to be rebuilt and will not run on .NET 5.0 as-is. Libraries that use WinRT APIs will need to multi-target to manage this difference between .NET Core 3.1 and .NET 5.0.
Going forward, we will rely on the new CsWinRT tool provided by the WinRT team in Windows. It generates C#-based WinRT interop assemblies, which can be delivered via NuGet. That’s exactly what the Windows team is doing, for the WinRT APIs in Windows. The tool can be used by anyone that wants to use WinRT (on Windows) as an interop system, to expose native APIs to .NET or .NET APIs to native code.
The CsWinRT tool is logically similar to tlbimp and tlbexp, although much better. The tlb tools relied on a lot of COM interop plumbing in the .NET runtime. The CsWinRT tool only relies on public .NET APIs. That said, the function pointers feature in C# 9 — and partially implemented in the .NET 5.0 runtime — was in part inspired by the needs of the CsWinRT tool.
There are several benefits to this new WinRT interop model:
- It can be developed and improved separate from the .NET runtime.
- It is symmetrical with the tool-based interop systems provided for other OSes, like iOS and Android.
- The tool can take advantage of other .NET features (AOT, C# features, IL linking), which was not an option for the previous system.
- Simplifies the .NET runtime codebase.
You don’t need to add NuGet references to use WinRT APIs. Targeting a Windows 10 TFM — just discussed in the .NET 5.0 TFM section earlier — is enough. If you target .NET Core 3.1 or earlier, you need to reference WinRT packages. You can see this pattern in the System.Device.Gpio project.
Native exports
We’ve had requests to enable exports for native binaries that calls into .NET code for a long time. The building block for the scenario is hosting API support for UnmanagedCallersOnlyAttribute.
This feature is a building-block for creating higher level experiences. Aaron Robinson, on our team, has been working on a .NET Native Exports project that provides a more complete experience for publishing .NET components as native libraries. We’re looking for feedback on this capability to help decide if the approach should be included in the product.
The .NET Native exports project enables you to:
- Expose custom native exports.
- Doesn’t require a higher-level interop technology like COM.
- Works cross-platform.
There are existing projects that enable similar scenarios, such as:
Over the years, we’ve seen a variety of hosting models for .NET in native applications. @rseanhall proposed and implemented a novel new model for doing that, which takes advantage of all the built-in application functionality offered by the .NET application hosting layer (specifically loading dependencies), while enabling a custom entrypoint to be called from native code. That’s perfect for a lot of scenarios, and that one can imagine becoming popular with developers that host .NET components from native applications. That didn’t exist before. Thanks for the contribution, @rseanhall.
Two primary PRs:
Event pipe
Event pipe is a new subsystem and API that we added in .NET Core 2.2 to make it possible to perform performance and other diagnostic investigations on any operating system. In .NET 5.0, the event pipe has been extended to enable profilers to write event pipe events. This scenario is critical for instrumenting profilers that previously relied on ETW (on Windows) to monitor application behavior and performance.
Assembly load information is now available via event pipe. This improvement is the start of making similar diagnostics functionality available as is part of .NET Framework, such as the Fusion Log Viewer. You can now use dotnet-trace to collect this information, using the following command:
dotnet-trace collect --providers Microsoft-Windows-DotNETRuntime:4:4 -- ./MyApp –my-arg 1
The workflow is described in the dotnet-trace docs. You can see assembly loading information for a simple test app.
Microsoft.Extensions.Logging
We’ve made improvements to the console log provider in the Microsoft.Extensions.Logging
library. You can now implement a custom ConsoleFormatter
to exercise complete control over formatting and colorization of the console output. The formatter APIs allow for rich formatting by implementing a subset of the VT-100
(supported by most modern terminals) escape sequences. The console logger can parse out escape sequences on unsupported terminals allowing you to author a single formatter for all terminals.
In addition to support for custom formatters, we’ve also added a built-in JSON formatter that emits structured JSON logs to the console.
Dump debugging
Debugging managed code requires knowledge of managed objects and constructs. The Data Access Component (DAC) is a subset of the runtime execution engine that has knowledge of these constructs and can access these managed objects without a runtime. .NET Core process dumps collected on Linux can now be analyzed on Windows using WinDBG or dotnet dump analyze
.
We’ve also added support for capturing ELF dumps from .NET processes running on macOS. Since ELF is not the native executable (native debuggers like lldb
will not work with these dumps) file format on macOS, we have made this an opt-in feature. To enable support for dump collection on macOS, set the environment variable COMPlus_DbgEnableElfDumpOnMacOS=1
. The resulting dumps can be analyzed using dotnet dump analyze
.
Printing environment information
As .NET has extended support for new operating systems and chip architectures, people sometimes want a way to print environment information. We created a simple .NET tool that does this, called dotnet-runtimeinfo.
You can install and run the tool with the following commands.
dotnet tool install -g dotnet-runtimeinfo
dotnet-runtimeinfo
The tool produces output in the following form for your environment.
**.NET information Version: 5.0.0FrameworkDescription: .NET 5.0.0Libraries version: 5.0.0Libraries hash: cf258a14b70ad9069470a108f13765e0e5988f51 **Environment information OSDescription: Linux 5.8.6-1-MANJARO-ARM #1 SMP Thu Sep 3 22:01:08 CEST 2020OSVersion: Unix 5.8.6.1OSArchitecture: Arm64ProcessorCount: 6 **CGroup info**cfs_quota_us: -1memory.limit_in_bytes: 9223372036854771712memory.usage_in_bytes: 2740666368
Runtime and Libraries
There were many improvements across the runtime and libraries.
Code quality improvements in RyuJIT
There were a lot of improvements to the JIT this release, many of which I shared in previous .NET 5.0 preview posts. In this post, I’m elevating the changes that came from the community.
- Use xmm for stack prolog – dotnet/runtime #32538 — Change to x86/x64 prolog zeroing code. Improvements: Json; TechEmpower. Credit: Ben Adams.
- Vectorise BitArray for Arm64 – dotnet/runtime #33749 — The BitArray class was updated to include a hardware-accelerated implementation for Arm64 using Arm64 intrinisics. The performance improvements for BitArray are very significant. Credit to @Gnbrkm41.
- Dynamic generic dictionary expansion feature dotnet/runtime #32270 – Some (maybe most?) uses of generics now have better performance (initial performance findings), based on improving the implementation of low-level (native code) dictionaries used by the runtime to store information about generic types and methods. See Perf: Collection Count() is Slower in Core than the CLR for more information. Credit to @RealDotNetDave for the bug report.
- Implement Vector.Ceiling / Vector.Floor dotnet/runtime #31993 – Implement Vector.Ceiling / Vector.Floor using x64 and Arm64 intrinsics, per API proposal. Credit to @Gnbrkm41.
- New, much faster, portable implementation of tailcall helpers. Credit: Jakob Botsch Nielsen (.NET team intern). Reaction from @dsymetweets.
- Add VectorTableList and TableVectorExtension intrinsics — Credit: @TamarChristinaArm (ARM Holdings)
- Improved Intel architecture performance using new hardware intrinsics BSF/BSR — Credit @saucecontrol
- Implement Vector{Size}.AllBitsSet — Credit @Gnbrkm41
- Enable eliding some bounds checks — Credit @nathan-moore
Garbage Collector
The following improvements were made in the GC.
- Card mark stealing – dotnet/coreclr #25986 — Server GC (on different threads) can now work-steal while marking gen0/1 objects held live by older generation objects. This means that ephemeral GC pauses are shorter for scenarios where some GC threads took much longer to mark than others.
- Introducing Pinned Object Heap – dotnet/runtime #32283 — Adds the Pinned Object Heap (POH). This new heap (a peer to the Large Object Heap (LOH)) will allow the GC to manage pinned objects separately, and as a result avoid the negative effects of pinned objects on the generational heaps.
- Allow allocating large object from free list while background sweeping SOH — Enabled LOH allocations using the free list while BGC is sweeping SOH. Previously this was only using end of segment space on LOH. This allowed for better heap usage.
- Background GC suspension fixes – dotnet/coreclr #27729 — Suspension fixes to reduce time for both BGC and user threads to be suspended. This reduces the total time it takes to suspend managed threads before a GC can happen. dotnet/coreclr #27578 also contributes to the same outcome.
- Fix named cgroup handling in docker — Added support to read limits from named cgroups. Previously we only read from the global one.
- Optimize vectorized sorting – dotnet/runtime #37159 — vectorized mark list sorting in GC which reduces the ephemeral GC pause time (also dotnet/runtime #40613).
- Generational aware analysis – dotnet/runtime #40322 — generational aware analysis that allows you to determine what old generation objects hold on to younger generation objects thus making them survive and contribute to ephemeral GC pause time.
- Optimize decommitting GC heap memory pages – dotnet/runtime #35896 — optimized decommit, much better decommit logic and for Server GC took decommit completely out of the “stop the world” phase which reduced blocking GC pause time.
The GC now exposes detailed information of the most recent collection, via the GC.GetGCMemoryInfo method. The GCMemoryInfo struct provides information about machine memory, heap memory and the most recent collection, or most recent collection of the kind of GC you specify – ephemeral, full blocking or background GC.
The most likely use cases for using this new API are for logging/monitoring or to indicate to a loader balancer that a machine should be taken out of rotation to request a full GC. It could also be used to avoid container hard-limits by reducing the size of caches.
Another, small but impactful change, was made to defer the expensive reset memory
operation to low-memory situations. We expect these changes in policy to lower the GC latency (and GC CPU usage in general).
Windows Arm64
.NET apps can now run natively on Windows Arm64. This follows the support we added for Linux Arm64 (support for glibc and musl) with .NET Core 3.0. With .NET 5.0, you can develop and run apps on Windows Arm64 devices, such as Surface Pro X. You can already run .NET Core and .NET Framework apps on Windows Arm64, but via x86 emulation. It’s workable, but native Arm64 execution has much better performance.
MSI installers for Arm64 were one of the final changes this release. You can see the .NET 5.0 SDK installer in the following image.
The .NET 5.0 SDK does not currently contain the Windows Desktop components — Windows Forms and WPF — on Windows Arm64. This change was initially shared in the .NET 5.0 Preview 8 post. We are hoping to add the Windows desktop pack for Windows Arm64 in a 5.0 servicing update. We don’t currently have a date to share. Until then, the SDK, console and ASP.NET Core applications are supported on Windows Arm64, but not Windows Desktop components.
Arm64 Performance
We’ve been investing significantly in improving Arm64 performance, for over a year. We’re committed to making Arm64 a high-performance platform with .NET. These improvements apply equally to Windows and Linux. Platform portability and consistency have always been compelling characteristics of .NET. This includes offering great performance wherever you use .NET. With .NET Core 3.x, Arm64 has functionality parity with x64 but is missing some key performance features and investments. We’ve resolved that in .NET 5.0, as described in Arm64 Performance in .NET 5.0.
The improvements:
- Tune JIT optimizations for Arm64 (example)
- Enable and take advantage of Arm64 hardware intrinsics (example).
- Adjust performance-critical algorithms in libraries for Arm64 (example).
See Improving Arm64 Performance in .NET 5.0 for more details.
Hardware intrinsics are a low-level performance feature we added in .NET Core 3.0. At the time, we added support for x86-64 instructions and chips. As part of .NET 5.0, we are extending the feature to support Arm64. Just creating the intrinsics doesn’t help performance. They need to be used in performance-critical code. We’ve taken advantage of Arm64 intrinsics extensively in .NET libraries in .NET 5.0. You can also do this in your own code, although you need to be familiar with CPU instructions to do so.
I’ll explain how hardware intrinsics work with an analogy. For the most part, developers rely on types and APIs built into .NET, like string.Split
or HttpClient
. Those APIs often take advantage of native operating system APIs, via the P/Invoke feature. P/Invoke enables high-performance native interop and is used extensively in the .NET libraries for that purpose. You can use this same feature yourself to call native APIs. Hardware intrinsics are similar, except instead of calling operating system APIs, they enable you to directly use CPU instructions in your code. It’s roughly equivalent to a .NET version of C++ intrinsics. Hardware intrinsics are best thought of as a CPU hardware-acceleration feature. They provide very tangible benefits and are now a key part of the performance substrate of the .NET libraries, and responsible for many of the benefits you can read about in the .NET 5.0 performance post. In terms of comparison to C++, when .NET intrinsics are AOT-compiled into Ready-To-Run files, the intrinsics have no runtime performance penalty.
Note: The Visual C++ compiler has an analogous intrinsics feature. You can directly compare C++ to .NET hardware intrinsics, as you can see if you search for _mm_i32gather_epi32
at System.Runtime.Intrinsics.X86.Avx2, x64 (amd64) intrinsics list, and Intel Intrinsics guide. You will see a lot of similarity.
We’re making our first big investments in Arm64 performance in 5.0, and we’ll continue this effort in subsequent releases. We work directly with engineers from Arm Holdings to prioritize product improvements and to design algorithms that best take advantage of the Armv8 ISA. Some of these improvements will accrue value to Arm32, however, we are not applying unique effort to Arm32. If you use a Raspberry Pi, you’ll enjoy these improvements if you install the new Arm64 version of Raspberry Pi OS.
We expect that Apple will announce new Apple Silicon-based Mac computers any day now. We already have early builds of .NET 6.0 for Apple Silicon and have been working with Apple engineers to help optimize .NET for that platform. We’ve also had some early community engagement on Apple Silicon (Credit @snickler).
P95+ Latency
We see an increasing number of large internet-facing sites and services being hosted on .NET. While there is a lot of legitimate focus on the requests per second (RPS) metric, we find that no big site owners ask us about that or require millions of RPS. We hear a lot about latency, however, specifically about improving P95 or P99 latency. Often, the number of machines or cores that are provisioned for (and biggest cost driver of) a site are chosen based on achieving a specific P95 metric, as opposed to say P50. We think of latency as being the true “money metric”.
Our friends at Stack Overflow do a great job of sharing data on their service. One of their engineers, Nick Craver, recently shared improvements they saw to latency, as a result of moving to .NET Core:
Pinned objects have been a long-term challenge for GC performance, specifically because they accelerate (or cause) memory fragmentation. We’ve added a new GC heap for pinned objects. The pinned object heap is based on the assumption that there are very few pinned objects in a process but that their presence causes disproportionate performance challenges. It makes sense to move pinned objects — particularly those created by .NET libraries as an implementation detail — to a unique area, leaving the generational GC heaps with few or no pinned objects, and with substantially higher performance as a result.
More recently, we’ve been attacking long-standing challenges in the GC. dotnet/runtime #2795 applies a new approach to GC statics scanning that avoids lock contention when it is determining liveness of GC heap objects. dotnet/runtime #25986 uses a new algorithm for balancing GC work across cores during the mark phase of garbage collection, which should increase the throughput of garbage collection with large heaps, which in turn reduces latency.
Improving tiered compilation performance
We’ve been working on improving tiered compilation for multiple releases. We continue to see it as a critical performance feature, for both startup and steady-state performance. We’ve made two big improvements to tiered compilation this release.
The primary mechanism underlying tiered compilation is call counting. Once a method is called n times, the runtime asks the JIT to recompile the method at higher quality. From our earliest performance analyses, we knew that the call-counting mechanism was too slow, but didn’t see a straightforward way to resolve that. As part of .NET 5.0, we’ve improved the call counting mechanism used by tiered JIT compilation to smooth out performance during startup. In past releases, we’ve seen reports of unpredictable performance during the first 10-15s of process lifetime (mostly for web servers). That should now be resolved.
Another performance challenge we found was using tiered compilation for methods with loops. The fundamental problem is that you can have a cold method (only called once or a few times; $lt; n) with a loop that iterates many times. We call this pathological scenario “cold method; hot loop”. It is easy to imagine this happening with the Main
method of an application. As a result, we disabled tiered compilation for methods with loops by default. Instead, we enabled applications to opt into using tiered compilation with loops. PowerShell is an application that chose to do this, after seeing high single-digit performance improvements in some scenarios.
To address methods with loops better, we implemented on-stack replacement (OSR). This is similar to a feature that the Java Virtual Machines has, of the same name. OSR enables code executed by a currently running method to be re-compiled in the middle of method execution, while those methods are active “on-stack”. This feature is currently experimental and opt-in, and on x64 only.
To use OSR, multiple features must be enabled. The PowerShell project file is a good starting point. You will notice that tiered compilation and all quick-jit features are enabled. In addition, you need to set the COMPlus_TC_OnStackReplacement
environment variable to 1
.
Alternatively, you can set the following two environment variables, assuming all other settings have their default values:
COMPlus_TC_QuickJitForLoops=1
COMPlus_TC_OnStackReplacement=1
We do not intend to enable OSR by default in .NET 5.0 and have not yet decided if we will support it in production.
Support for ICU on Windows
We use the ICU library for Unicode and globalization support, previously only on Linux and macOS. We are now using this same library on Windows 10. This change makes the behavior of globalization APIs such as culture-specific string comparison consistent between Windows 10, macOS, and Linux. We also use ICU with Blazor WebAssembly.
Expanding System.DirectoryServices.Protocols to Linux and macOS
We’ve been adding cross-platform support for System.DirectoryServices.Protocols. This includes support for Linux and support for macOS. Windows support was pre-existing.
System.DirectoryServices.Protocols is a lower-level API than System.DirectoryServices, and enables (or can be used to enable) more scenarios. System.DirectoryServices includes Windows-only concepts/implementations, so it was not an obvious choice to make cross-platform. Both API-sets enable controlling and interacting with a directory service server, like LDAP or Active Directory.
System.Text.Json
System.Text.Json
has been significantly improved in .NET 5.0 to improve performance, reliability, and to make it easier for people to adopt that are familiar with Newtonsoft.Json. It also includes support for deserializing JSON objects to records.
If you are looking at using System.Text.Json
as an alternative to Newtonsoft.Json
, you should check out the migration guide. The guide clarifies the relationship between these two APIs. System.Text.Json
is intended to cover many of the same scenarios as Newtonsoft.Json
, but it’s not intended to be a drop-in replacement for or achieve feature parity with the popular JSON library. We try to maintain a balance between performance and usability, and bias to performance in our design choices.
HttpClient
extension methods
JsonSerializer
extension methods are now exposed on HttpClient
and greatly simplify using these two APIs together. These extension methods remove complexity and take care of a variety of scenarios for you, including handling the content stream and validating the content media type. Steve Gordon does a great job of explaining the benefits in Sending and receiving JSON using HttpClient with System.Net.Http.Json.
The following example deserializes weather forecast JSON data into a Forecast
record, using the new
using System;using System.Net.Http;using System.Net.Http.Json;string serviceURL = "https://localhost:5001/WeatherForecast";HttpClient client = new();Forecast[] forecasts = await client.GetFromJsonAsync<Forecast[]>(serviceURL);foreach(Forecast forecast in forecasts){ Console.WriteLine($"{forecast.Date}; {forecast.TemperatureC}C; {forecast.Summary}");}// {"date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"temperatureF":31,"summary":"Scorching"} public record Forecast(DateTime Date, int TemperatureC, int TemperatureF, string Summary);view rawProgram.cs hosted with
by GitHub
This code is compact! It is relying on top-level programs and records from C# 9 and the new GetFromJsonAsync<T>()
extension method. The use of foreach
and await
in such close proximity might be make you wonder if we’re going to add support for streaming JSON objects. I really hope so.
You can try this on your own machine. The following .NET SDK commands will create a weather forecast service using the WebAPI template. It will expose the service at the following URL by default: https://localhost:5001/WeatherForecast
. This is the same URL used in the sample.
rich@thundera ~ % dotnet new webapi -o webapi
rich@thundera ~ % cd webapi
rich@thundera webapi % dotnet run
Make sure you’ve run dotnet dev-certs https --trust
first or the handshake between client and server won’t work. If you’re having trouble, see Trust the ASP.NET Core HTTPS development certificate.
You can then run the previous sample.
rich@thundera ~ % git clone https://gist.github.com/3b41d7496f2d8533b2d88896bd31e764.git weather-forecast
rich@thundera ~ % cd weather-forecast
rich@thundera weather-forecast % dotnet run
9/9/2020 12:09:19 PM; 24C; Chilly
9/10/2020 12:09:19 PM; 54C; Mild
9/11/2020 12:09:19 PM; -2C; Hot
9/12/2020 12:09:19 PM; 24C; Cool
9/13/2020 12:09:19 PM; 45C; Balmy
Improved support for immutable types
There are multiple patterns for defining immutable types. Records are just the newest one. JsonSerializer
now has support for immutable types.
In this example, you’ll see the serialization with an immutable struct.
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
var json = "{"date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"temperatureF":31,"summary":"Scorching"} ";
var options = new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
IncludeFields = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var forecast = JsonSerializer.Deserialize<Forecast>(json, options);
Console.WriteLine(forecast.Date);
Console.WriteLine(forecast.TemperatureC);
Console.WriteLine(forecast.TemperatureF);
Console.WriteLine(forecast.Summary);
var roundTrippedJson = JsonSerializer.Serialize<Forecast>(forecast, options);
Console.WriteLine(roundTrippedJson);
public struct Forecast{
public DateTime Date {get;}
public int TemperatureC {get;}
public int TemperatureF {get;}
public string Summary {get;}
[JsonConstructor]
public Forecast(DateTime date, int temperatureC, int temperatureF, string summary) => (Date, TemperatureC, TemperatureF, Summary) = (date, temperatureC, temperatureF, summary);
}
Note: The JsonConstructor
attribute is required to specify the constructor to use with structs. With classes, if there is only a single constructor, then the attribute is not required. Same with records.
It produces the following output:
rich@thundera jsonserializerimmutabletypes % dotnet run
9/6/2020 11:31:01 AM
-1
31
Scorching
{"date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"temperatureF":31,"summary":"Scorching"}
Support for records
JsonSerializer
support for records is almost the same as what I just showed you for immutable types. The difference I want to show here is deserializing a JSON object to a record that exposes a parameterized constructor and an optional init property.
Here’s the program, including the record definition:
using System;using System.Text.Json;Forecast forecast = new(DateTime.Now, 40){ Summary = "Hot!"};string forecastJson = JsonSerializer.Serialize<Forecast>(forecast);Console.WriteLine(forecastJson);Forecast? forecastObj = JsonSerializer.Deserialize<Forecast>(forecastJson);Console.Write(forecastObj);public record Forecast (DateTime Date, int TemperatureC){ public string? Summary {get; init;}};view rawProgram.cs hosted with
by GitHub
It produces the following output:
rich@thundera jsonserializerrecords % dotnet run
{"Date":"2020-09-12T18:24:47.053821-07:00","TemperatureC":40,"Summary":"Hot!"}
Forecast { Date = 9/12/2020 6:24:47 PM, TemperatureC = 40, Summary = Hot! }
Improved Dictionary<K,V>
support
JsonSerializer
now supports dictionaries with non-string keys. You can see what this looks like in the following sample. With .NET Core 3.0, this code compiles but throws a NotSupportedException
.
using System;using System.Collections.Generic;using System.Text.Json;Dictionary<int, string> numbers = new (){ {0, "zero"}, {1, "one"}, {2, "two"}, {3, "three"}, {5, "five"}, {8, "eight"}, {13, "thirteen"}, {21, "twenty one"}, {34, "thirty four"}, {55, "fifty five"},};var json = JsonSerializer.Serialize<Dictionary<int, string>>(numbers);Console.WriteLine(json);var dictionary = JsonSerializer.Deserialize<Dictionary<int, string>>(json);Console.WriteLine(dictionary[55]);view rawProgram.cs hosted with
by GitHub
It produces the following output.
rich@thundera jsondictionarykeys % dotnet run
{"0":"zero","1":"one","2":"two","3":"three","5":"five","8":"eight","13":"thirteen","21":"twenty one","34":"thirty four","55":"fifty five"}
fifty five
Support for fields
JsonSerializer
now supports fields. This change was contributed by @YohDeadfall. Thanks!
You can see what this looks like in the following sample. With .NET Core 3.0, JsonSerializer
fails to serialize or deserialize with types that use fields. This is a problem for existing types that have fields and cannot be changed. With this change, that’s no longer an issue.
using System;using System.Text.Json;var json = "{\"date\":\"2020-09-06T11:31:01.923395-07:00\",\"temperatureC\":-1,\"temperatureF\":31,\"summary\":\"Scorching\"} "; var options = new JsonSerializerOptions(){ PropertyNameCaseInsensitive = true, IncludeFields = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase};var forecast = JsonSerializer.Deserialize<Forecast>(json, options);Console.WriteLine(forecast.Date);Console.WriteLine(forecast.TemperatureC);Console.WriteLine(forecast.TemperatureF);Console.WriteLine(forecast.Summary);var roundTrippedJson = JsonSerializer.Serialize<Forecast>(forecast, options);Console.WriteLine(roundTrippedJson);public class Forecast{ public DateTime Date; public int TemperatureC; public int TemperatureF; public string Summary;}view rawProgram.cs hosted with
by GitHub
Produces the following output:
rich@thundera jsonserializerfields % dotnet run
9/6/2020 11:31:01 AM
-1
31
Scorching
{"date":"2020-09-06T11:31:01.923395-07:00","temperatureC":-1,"temperatureF":31,"summary":"Scorching"}
Preserving references in JSON object graphs
JsonSerializer
has added support for preserving (circular) references within JSON object graphs. It does this by storing IDs that can be reconstituted when a JSON string is deserialized back to objects.
using System;using System.Collections.Generic;using System.Text.Json;using System.Text.Json.Serialization;Employee janeEmployee = new(){ Name = "Jane Doe", YearsEmployed = 10};Employee johnEmployee = new(){ Name = "John Smith"};janeEmployee.Reports = new List<Employee> { johnEmployee };johnEmployee.Manager = janeEmployee;JsonSerializerOptions options = new(){ // NEW: globally ignore default values when writing null or default DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, // NEW: globally allow reading and writing numbers as JSON strings NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString, // NEW: globally support preserving object references when (de)serializing ReferenceHandler = ReferenceHandler.Preserve, IncludeFields = true, // NEW: globally include fields for (de)serialization WriteIndented = true,};string serialized = JsonSerializer.Serialize(janeEmployee, options);Console.WriteLine($"Jane serialized: {serialized}");Employee janeDeserialized = JsonSerializer.Deserialize<Employee>(serialized, options);Console.Write("Whether Jane's first report's manager is Jane: ");Console.WriteLine(janeDeserialized.Reports[0].Manager == janeDeserialized);public class Employee{ // NEW: Allows use of non-public property accessor. // Can also be used to include fields "per-field", rather than globally with JsonSerializerOptions. [JsonInclude] public string Name { get; internal set; } public Employee Manager { get; set; } public List<Employee> Reports; public int YearsEmployed { get; set; } // NEW: Always include when (de)serializing regardless of global options [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public bool IsManager => Reports?.Count > 0;}view rawProgram.cs hosted with
by GitHub
Performance
JsonSerializer
performance is significantly improved in .NET 5.0. Stephen Toub covered some JsonSerializer
improvements in his Performance Improvements in .NET 5 post. I also covered Json Performance in more detailed in the .NET 5.0 RC1 post.
Application deployment
After writing or updating an application, you need to deploy it for your users to benefit. This might be to a web server, a cloud service, or client machine, and might be the result of a CI/CD flow using a service like Azure DevOps or GitHub Actions.
We strive to provide first-class deployment capabilities that naturally align with the application types. For .NET 5.0, we focused on improving single file applications, reducing container size for docker multi-stage builds, and providing better support for deploying ClickOnce applications with .NET Core.
Containers
We consider containers to be the most important cloud trend and have been investing significantly in this modality. We’re investing in containers in multiple ways, at multiple levels of the .NET software stack. The first is our investment in fundamentals, which is increasingly influenced by the container scenario and by developers who deploy containerized apps.
We’re making it easier to work with container orchestrators. We’ve added OpenTelemetry support so that you can capture distributed traces and metrics from your application. dotnet-monitor is a new tool that is intended as the primary way to access diagnostic information from a .NET process. In particular, we have started building a container variant of dotnet-monitor that you can use as an application sidecar. Last, we are building dotnet/tye as way to improve microservices developer productivity, both for development and deploying to a Kubernetes environment.
The .NET runtime now has support for cgroup v2, which we expect will become an important container-related API beyond 2020. Docker currently uses cgroup v1 (which is already supported by .NET). In comparison, cgroup v2 is simpler, more efficient, and more secure than cgroup v1. You can learn more about cgroup and Docker resource limits from our 2019 Docker update. Linux distros and containers runtimes are in the process of adding support for cgroup v2. .NET 5.0 will work correctly in cgroup v2 environments once they become more common. Credit to Omair Majid, who supports .NET at Red Hat.
We’re now publishing Windows Server Core images, in addition to Nano Server. We added Server Core because we heard feedback from customers who wanted a .NET image that was fully compatible with Windows Server. If you need that, then this new image is for you. It’s supported for the combination of: Windows Server 2019 Long-Term Servicing Channel (LTSC), .NET 5.0, and x64. We’ve made other changes that reduce the size of Windows Server Core images. Those improvements make a big difference but were made after Windows Server 2019 was released. They will, however, benefit the next Windows Server LTSC release.
As part of the move to “.NET” as the product name, we’re now publishing .NET Core 2.1, 3.1 and .NET 5.0 images to the mcr.microsoft.com/dotnet
family of repos, instead of mcr.microsoft.com/dotnet/core
. We will continue dual publishing .NET Core 2.1 and 3.1 to the previous location while those versions are supported. .NET 5.0 images will only be published to the new locations. Please update your FROM
statements and scripts accordingly.
As part of .NET 5.0, we re-based the SDK image on top of the ASP.NET image instead of buildpack-deps to dramatically reduces the size of the aggregate images you pull in multi-stage build scenarios.
This change has the following benefit for multi-stage builds, with a sample Dockerfile:
Multi-stage build costs with Ubuntu 20.04 Focal:
Pull ImageBeforeAftersdk:5.0-focal
268 MB232 MBaspnet:5.0-focal
64 MB10 KB (manifest only)
Net download savings: 100 MB (-30%)
Multi-stage build costs with Debian 10 Buster:
Pull ImageBeforeAftersdk:5.0
280 MB218 MBaspnet:5.0
84 MB4 KB (manifest only)
Net download savings: 146 MB (-40%)
See dotnet/dotnet-docker #1814 for more detailed information.
This change helps multi-stage builds, where the sdk
and the aspnet
or runtime
image you are targeting are the same version (we expect that this is the common case). With this change, the aspnet
pull (for example), will be a no-op, because you will have pulled the aspnet
layers via the initial sdk
pull.
We made similar changes for Alpine and Nano Server. There is no buildpack-deps
image for either Alpine or Nano Server. However, the sdk
images for Alpine and Nano Server were not previously built on top of the ASP.NET image. We fixed that. You will see significant size wins for Alpine and Nano Server as well with 5.0, for multi-stage builds.
Single file applications
Single file applications are published and deployed as a single file. The app and its dependencies are all included within that file. When the app is run, the dependencies are loaded directly from that file into memory (with no performance penalty).
In .NET 5.0, single file apps are primarily focused on Linux (more on that later). They can be either framework-dependent or self-contained. Framework-dependent single file apps can be very small, by relying on a globally-installed .NET runtime. Self-contained single-file apps are larger (due to carrying the runtime), but do not require installation of the .NET runtime as an installation pre-step and will just work as a result. In general, framework-dependent is good for development and enterprise environments, while self-contained is often a better choice for ISVs.
We produced a version of single-file apps with .NET Core 3.1. It packages binaries into a single file for deployment and then unpacks those files to a temporary directory to load and execute them. There may be some scenarios where this approach is better, but we expect that the solution we’ve built for 5.0 will be preferred and a welcome improvement.
We had multiple hurdles to overcome to create a true single-file solution. Key tasks were creating a more sophisticated application bundler and teaching the runtime to load assemblies out of binary resources. We also ran into some hurdles that we could not clear.
On all platforms, we have a component called “apphost”. This is the file that becomes your executable, for example myapp.exe
on Windows or ./myapp
on Unix-based platforms. For single file apps we created a new apphost we call “superhost”. It has the same role as the regular apphost, but also includes a statically-linked copy of the runtime. The superhost is a fundamental design point of our single file approach. This model is the one we use on Linux with .NET 5.0. We were not able to implement this approach on Windows or macOS, due to various operating system constraints. We do not have a superhost on Windows or macOS. On those operating systems, the native runtime binaries (~3 of them) sit beside the single file app (resulting in “not single file”). We’ll revisit this situation in .NET 6.0, however, we expect the problems we ran into to remain challenging.
You can use the following commands to produce single-file apps.
- Framework-dependent single-file app:
dotnet publish -r linux-x64 --self-contained false /p:PublishSingleFile=true
- Self-contained single-file app:
dotnet publish -r linux-x64 --self-contained true /p:PublishSingleFile=true
You can also configure single file publishing with a project file.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<!-- The OS and CPU type you are targeting -->
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<!-- Determine self-contained or framework-dependent -->
<SelfContained>true</SelfContained>
<!-- Enable single file -->
<PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>
</Project>
You can experiment with assembly trimming to reduce the size of your application. It can break apps, by over-trimming, so it is recommended to test your app thoroughly after using this feature. Assembly trimming also removes ahead-of-time-compiled read-to-run native code (for the assemblies that are trimmed), which is there primarily for performance. You will want to test your application for performance after trimming. You can ready-to-run-compile your app after trimmng by using PublishReadyToRun
property (and setting to true
).
Notes:
- Apps are OS and architecture-specific. You need to publish for each configuration (Linux x64, Linux Arm64, Windows x64, …).
- Configuration files (like
*.runtimeconfig.json
) are included in the single file. You can place an additional config file beside the single file, if needed (possibly for testing). .pdb
files are not included in the single file by default. You can enable PDB embedding with the<DebugType>embed</DebugType>
property.- The
IncludeNativeLibrariesForSelfExtract
property can be used to embed native runtime binaries, on Windows and macOS, however, they have to be unpacked to temporary storage at runtime. This feature is not recommended for genereal use.
ClickOnce
ClickOnce has been a popular .NET deployment option for many years. It’s now supported for .NET Core 3.1 and .NET 5.0 Windows apps. We knew that many people would want to use ClickOnce for application deployment when we added Windows Forms and WPF support to .NET Core 3.0. In the past year, the .NET and Visual Studio teams worked together to enable ClickOnce publishing, both at the command line and in Visual Studio.
We had two goals from the start of the project:
- Enable a familiar experience for ClickOnce in Visual Studio.
- Enable a modern CI/CD for ClickOnce publishing with command-line flows, with either MSBuild or the Mage tool.
It’s easiest to show you the experience in pictures.
Let’s start with the Visual Studio experience, which is centered around project publishing.
The primary deployment model we’re currently supporting is framework dependent apps. It is easy to take a dependency on the .NET Desktop Runtime (that’s the one that contains WPF and Windows Forms). Your ClickOnce installer will install the .NET runtime on user machines if it is needed. We also intend to support self-contained and single file apps.
You might wonder if you can still be able to take advantage of ClickOnce offline and updating features. Yes, you can.
The big change with Mage is that it is now a .NET tool, distributed on NuGet. That means you don’t need to install anything special on your machine. You just need the .NET 5.0 SDK and then you can install Mage as a .NET tool. You can use it to publish .NET Framework apps as well, however, SHA1 signing and partial trust support have been removed.
The Mage installation command follows:
dotnet tool install -g Microsoft.DotNet.Mage
After you’ve produced and distributed your ClickOnce installer, your users will see the familiar ClickOnce installation dialogs.
Your users will see the update dialog when you make updates available.
Closing
.NET 5.0 is another big release that should improve many aspects of your use with .NET. We have enabled a broad set of improvements, ranging from single file applications, to performance, to Json serialization usability to Arm64 enablement. While today may be your first day with .NET 5.0, we’ve been running .NET 5.0 in production at Microsoft for months. We have confidence that it is ready for you to use, to run your business and power your apps. The new language improvements in C# 9 and F# 5 should make your code more expressive and easier to write. .NET 5.0 is also a great choice for your existing apps. In many cases, you can upgrade without much effort.
If you’re interested in performance, you may be interested in our progress with the TechEmpower benchmarks. Looking back, you can see that .NET Core 3.1 is doing pretty well with round 19, the latest round. We’re looking forward to seeing .NET 5.0 in the upcoming round 20. The new ranking will be something to watch out for when round 20 is finalized and published.
The improvements in .NET 5.0 are the result of efforts by many people, working collaboratively across the world and in many time zones, on GitHub. Thanks to everyone who contributed to this release. And don’t worry, there are lots of opportunities to contribute. The .NET 5.0 release has come to a close, but the next release has already started.
In Summary
The idea behind .NET 5 was to make .NET development simpler, but also richer in every sense. Microsoft is determined to bring more power and functionalities to their product. All new C# versions will also be part of .NET 5.
This will make every .NET dev’s life a lot easier. The days of learning new languages for specific cases are going to become a thing of the past. Once .NET 5 launches, all you’ll need to know is C# in order to create apps that run anywhere from IoT to Cloud.
However, there will be people who’ll experience some difficulties in this transition.
At Assemblysoft we specialise in Custom Software Development tailored to your requirements. We can onboard and add value to your business rapidly. We are an experienced Full-stack development team able to provide specific technical expertise or manage your project requirements end to end. We specialise in the Microsoft cloud and .NET Solutions and Services. Our developers are Microsoft Certified. We have real-world experience developing .NET applications and Azure Services for a large array of business domains. If you would like some assistance with Azure | Azure DevOps Services | Blazor Development | .NET MAUI Development or in need of custom software development, from an experienced development team in the United Kingdom, then please get in touch, we would love to add immediate value to your business.
Assemblysoft - Your Safe Pair of Hands