https://devblogs.microsoft.com/dotnet/announcing-net-6/
Skip to main content
[RE1Mu3b] Microsoft
.NET Blog
.NET Blog
.NET Blog
* Home
* DevBlogs
* Developer
+ Visual Studio
+ Visual Studio Code
+ Visual Studio for Mac
+ DevOps
+ Developer support
+ CSE Developer
+ Engineering@Microsoft
+ Azure SDK
+ IoT
+ Command Line
+ Perf and Diagnostics
+ Dr. International
+ Notification Hubs
+ Math in Office
* Technology
+ DirectX
+ PIX
+ SurfaceDuo
+ Startups
+ Sustainable Engineering
+ Windows AI Platform
* Languages
+ C++
+ C#
+ F#
+ Visual Basic
+ TypeScript
+ PowerShell Community
+ PowerShell Team
+ Python
+ Q#
+ JavaScript
+ Java
+ Java Blog in Chinese
* .NET
+ .NET
+ .NET MAUI
+ Blazor
+ ASP.NET
+ NuGet
+ Xamarin
* Platform Development
+ #ifdef Windows
+ Apps for Windows
+ Azure Depth Platform
+ Azure Government
+ Bing Dev Center
+ Microsoft Edge Dev
+ Microsoft Azure
+ Microsoft 365 Developer
+ Old New Thing
+ Windows MIDI and Music dev
+ Windows Search Platform
* Data Development
+ Azure Cosmos DB
+ Azure Data Studio
+ Azure SQL
+ OData
+ Revolutions R
+ SQL Server Data Tools
* More
[ ] Search Search
Cancel
.NET Conf 2021 .NET Conf is coming! November 9th through the 11th is
a free, three-day, virtual developer event that celebrates the major
releases of the .NET development platform.
Save the Date!
Close
Announcing .NET 6 -- The Fastest .NET Yet
[png]
Richard
November 8th, 2021
Welcome to .NET 6. Today's release is the result of just over a
year's worth of effort by the .NET Team and community. C# 10 and F# 6
deliver language improvements that make your code simpler and better.
There are massive gains in performance, which we've seen dropping the
cost of hosting cloud services at Microsoft. .NET 6 is the first
release that natively supports Apple Silicon (Arm64) and has also
been improved for Windows Arm64. We built a new dynamic
profile-guided optimization (PGO) system that delivers deep
optimizations that are only possible at runtime. Cloud diagnostics
have been improved with dotnet monitor and OpenTelemetry. WebAssembly
support is more capable and performant. New APIs have been added, for
HTTP/3, processing JSON, mathematics, and directly manipulating
memory. .NET 6 will be supported for three years. Developers have
already started upgrading applications to .NET 6 and we've heard
great early results in production. .NET 6 is ready for your app.
You can download .NET 6 for Linux, macOS, and Windows.
* Installers and binaries
* Container images
* Linux packages
* Release notes
* API diff
* Known issues
* GitHub issue tracker
See the ASP.NET Core post for what's new for web scenarios.
Visual Studio 2022 is also releasing today. Read the announcement and
watch the launch event to learn more about the release.
PowerShell 7.2 is also releasing today, built on .NET 6. PowerShell
users get access to the same performance improvements and APIs as
.NET developers.
.NET Conf is a free, three-day, virtual developer event that
celebrates the major releases of .NET. It starts tomorrow and runs
November 9-11 featuring speakers from our team, teams at Microsoft,
and the broader community with over 80 sessions. Tune in to learn and
engage with us.
Check out the new conversations posts for in-depth
engineer-to-engineer discussions on the latest .NET features.
.NET 6 Highlights
.NET 6 is:
* Production stress-tested with Microsoft services, cloud apps run
by other companies, and open source projects.
* Supported for three years as the latest long term support (LTS)
release.
* Unified platform across browser, cloud, desktop, IoT, and mobile
apps, all using the same .NET Libraries and the ability to share
code easily.
* Performance is greatly improved across the board and for file I/O
in particular, which together result in decreased execution time,
latency, and memory use.
* C# 10 offers language improvements such as record structs,
implicit using, and new lambda capabilities, while the compiler
adds incremental source generators. F# 6 adds new features
including Task based async, pipeline debugging and numerous
performance improvements.
* Visual Basic has improvements in the Visual Studio experience and
for Windows Forms project open experience.
* Hot Reload enables you to skip rebuilding and restarting your app
to view a new change -- while your app is running -- supported in
Visual Studio 2022 and from the .NET CLI, for C# and Visual
Basic.
* Cloud diagnostics have been improved with OpenTelemetry and
dotnet monitor, which is now supported in production and
available with Azure App Service.
* JSON APIs are more capable and have higher performance with a
source generator for the serializer.
* Minimal APIs introduced in ASP.NET Core to simplify the getting
started experience and improve the performance of HTTP services.
* Blazor components can now be rendered from JavaScript and
integrated with existing JavaScript based apps.
* WebAssembly AOT compilation for Blazor WebAssembly (Wasm) apps,
as well as support for runtime relinking and native dependencies.
* Single-page apps built with ASP.NET Core now use a more flexible
pattern that can be used with Angular, React, and other popular
frontend JavaScript frameworks.
* HTTP/3 has been added so that ASP.NET Core, HttpClient, and gRPC
can all interact with HTTP/3 clients and servers.
* File IO now has support for symbolic links and has greatly
improved performance with a re-written-from-scratch FileStream.
* Security has been improved with support for OpenSSL 3, the
ChaCha20Poly1305 encryption scheme, and runtime defense-in-depth
mitigations, specifically W^X and CET.
* Single-file apps (extraction-free) can be published for Linux,
macOS, and Windows (previously only Linux).
* IL trimming is now more capable and effective, with new warnings
and analyzers to ensure correct final results.
* Source generators and analyzers have been added that help you
produce better, safer, and higher performance code.
* Source build enables organizations like Red Hat to build .NET
from source and offer their own builds to their users.
The release includes about ten thousand git commits. Even with the
length of this post, it skips over many improvements. You'll have to
download and try .NET 6 to see everything that's new.
Support
.NET 6 is a Long-term Support (LTS) release that will be supported
for three years. It is supported on multiple operating systems,
including macOS Apple Silicon and Windows Arm64.
Red Hat supports .NET on Red Hat Enterprise Linux, in collaboration
with the .NET Team. On RHEL 8 and later, .NET 6 will be available for
the AMD and Intel (x64_64), ARM (aarch64), and IBM Z and LinuxONE
(s390x) architectures.
Please start migrating your apps to .NET 6, particularly .NET 5 apps.
We have heard from early adopters that upgrading to .NET 6 is
straightforward from .NET Core 3.1 and .NET 5.
.NET 6 is supported with Visual Studio 2022 and Visual Studio 2022
for Mac. It is not supported with Visual Studio 2019, Visual Studio
for Mac 8, or MSBuild 16. If you want to use .NET 6, you will need to
upgrade to Visual Studio 2022. .NET 6 is supported with the Visual
Studio Code C# extension.
Azure App Service:
* Azure Functions now supports running serverless functions in .NET
6.
* The App Service .NET 6 GA Announcement has information and
details for ASP.NET Core developers excited to get going with
.NET 6 today.
* Azure Static Web Apps now supports full-stack .NET 6 applications
with Blazor WebAssembly frontends and Azure Function APIs.
Note: If you're app is already running a .NET 6 Preview or RC build
on App Service, it will be auto-updated on the first restart once the
.NET 6 runtime and SDK are deployed to your region. If you deployed a
self-contained app, you will need to re-build and re-deploy.
Unified and extended platform
.NET 6 delivers a unified platform, for browser, cloud, desktop, IoT,
and mobile apps. The underlying platform has been updated to serve
the needs of all app types and to make it easy to re-use code across
all your apps. New capabilities and improvements are available to all
apps at the same time, so that your code running in the cloud or on a
mobile device behaves the same way and has the same benefits.
dotnet-unified-platform
The reach of .NET developers continues to widen with each release.
Machine learning and WebAssembly are two of the most recent
additions. For example, with machine learning, you can write apps
that look for anomalies in streaming data. With WebAssembly, you can
host .NET apps in the browser, just like HTML and JavaScript, or mix
them with HTML and JavaScript.
One of the most exciting additions is .NET Multi-platform App UI
(.NET MAUI). You can now write code -- in a single project -- that
delivers a modern client app experience across desktop and mobile
operating systems. .NET MAUI will be released a little later than
.NET 6. We've put a lot of time and effort into .NET MAUI and are
very excited to release it and see .NET MAUI apps in production.
Of course, .NET apps are also at home on Windows desktop -- with
Windows Forms and WPF -- and in the cloud with ASP.NET Core. They are
the app types we've offered for the longest and they continue to be
very popular, and we've improved them in .NET 6.
Targeting .NET 6
Continuing on the theme of a broad platform, writing .NET code across
all those operating systems is easy.
To target .NET 6, you need to use a .NET 6 target framework, like the
following:
net6.0
The net6.0 Target Framework Moniker (TFM) gives you access to all the
cross-platform APIs that .NET offers. This is the best option if you
are writing console apps, ASP.NET Core apps, or reusable
cross-platform libraries.
If you are targeting a specific operating system (like if writing a
Windows Forms or iOS app), then there is another set of TFMs (that
each target a self-evident operating system) for you to use. They
give you access to all the APIs in net6.0 plus a bunch of
operating-system-specific ones.
* net6.0-android
* net6.0-ios
* net6.0-maccatalyst
* net6.0-tvos
* net6.0-windows
The version-less TFMs are each equivalent to targeting the lowest
supported operating system version by .NET 6. You can specify an
operating system version if you want to be specific or to get access
to newer APIs.
The net6.0 and net6.0-windows TFMs are supported (same as .NET 5).
The Android and Apple TFMs are new with .NET 6 and currently in
preview. They will be supported with a later .NET 6 update.
There are no compatibility relationships between the OS-specific
TFMs. For example, net6.0-ios is not compatible with net6.0-tvos. If
you want to share code, you need to do that with source with #if
statements or binaries with net6.0 targeted code.
Performance
The team has had a deep and growing focus on performance ever since
we started the .NET Core project. Stephen Toub does an amazing job of
capturing the progress of .NET performance with each release. If you
haven't had the chance, I recommend taking a look at his Performance
improvements in .NET 6 post.
In this post, I've captured some heavy-hitter performance
improvements that you'll want to know about, including File IO,
interface casting, PGO, and System.Text.Json.
Dynamic PGO
Dynamic Profile-guided Optimization (PGO) can markedly improve
steady-state performance. For example, PGO gives a 26% improvement
(510K -> 640K) in requests per second for the TechEmpower JSON "MVC"
suite.
Dynamic PGO builds upon Tiered Compilation, which enables methods to
first be compiled very quickly (referred to as "Tier 0") to improve
startup performance, and to then subsequently be recompiled (referred
to as "Tier 1") with lots of optimization enabled once that method
has shown to be impactful. This model enables methods to be
instrumented in Tier 0 to allow various observations to be made about
the code's execution. When these methods are rejitted at Tier 1, the
information gathered from the Tier 0 executions is used to better
optimize the Tier 1 code. That's the essence of the mechanism.
Dynamic PGO will have slightly slower startup times than the default
runtime, as there is extra code running in Tier 0 methods to observe
method behavior.
To enable Dynamic PGO, set DOTNET_TieredPGO=1 in the environment
where your application will run. You must also ensure that Tiered
Compilation is enabled (it is by default). Dynamic PGO is opt-in
because it is a new and impactful technology. We want a release of
opt-in use and associated feedback to ensure that it is fully
stress-tested. We did the same thing with Tiered Compilation. Dynamic
PGO is supported and is already in use in production by at least one
very large Microsoft service. We encourage you to try it.
You can see more on dynamic PGO benefits in Performance in .NET 6
post, including the following microbenchmark, which measures the cost
of a particular LINQ enumerator.
private IEnumerator _source = Enumerable.Range(0, long.MaxValue).GetEnumerator();
[Benchmark]
public void MoveNext() => _source.MoveNext();
Here's the result, with and without dynamic PGO.
Method Mean Code Size
PGO Disabled 1.905 ns 30 B
PGO Enabled 0.7071 ns 105 B
That's a pretty big difference, but there is also increased code
size, which might surprise some readers. This is the size of assembly
code generated by the JIT, not memory allocations (which is a more
common focus). There is a good explanation for that from the .NET 6
Performance post.
One optimization common in PGO implementations is "hot/cold
splitting", where sections of a method frequently executed ("hot")
are moved close together at the beginning of the method, and sections
of a method infrequently executed ("cold") are moved to the end of
the method. That enables better use of instruction caches and
minimizes loads of likely-unused code.
As context, interface dispatch is the most expensive call type in
.NET. Non-virtual method calls are the fastest, and even faster still
are calls that can be eliminated via inlining. In this case, dynamic
PGO is providing two (alternative) callsites for MoveNext. The first
-- the hot one -- is a direct call to Enumerable+RangeIterator.MoveNext
and the other -- the cold one -- is a virtual interface call via
IEnumerator. It's a huge win if the hot one gets called most of
the time.
This is the magic. When the JIT instrumented the Tier 0 code for this
method, that included instrumenting this interface dispatch to track
the concrete type of _source on each invocation. And the JIT found
that every invocation was on a type called Enumerable+RangeIterator,
which is a private class used to implement Enumerable.Range inside of
the Enumerable implementation. As such, for Tier 1 the JIT has
emitted a check to see whether the type of _source is that
Enumerable+RangeIterator: if it isn't, then it jumps to the cold
section we previously highlighted that's performing the normal
interface dispatch. But if it is -- which based on the profiling data
is expected to be the case the vast majority of the time -- it can
then proceed to directly invoke the Enumerable+RangeIterator.MoveNext
method, non-virtualized. Not only that, but it decided it was
profitable to inline that MoveNext method. The net effect is that the
generated assembly code is bit larger, but optimized for the exact
scenario expected to be most common. Those are the kind of wins we
intended when we started building dynamic PGO.
Dynamic PGO is discussed again in the RyuJIT section.
File IO Improvements
FileStream was almost completely re-written in .NET 6, with a focus
on improving async File IO performance. On Windows, the
implementation no longer uses blocking APIs and can be up to a few
times faster! We've also made improvements to memory usage, on all
platforms. After the first async operation (which typically
allocates), we've made async operations allocation-free! In addition,
we have made the behavior for edge cases uniform where Windows and
Unix implementations were different (and it was possible).
The performance improvements of this re-write benefit all operating
systems. The benefit to Windows is the highest since it was farther
behind. macOS and Linux users should also see significantly
FileStream performance improvements.
The following benchmark writes 100 MB to a new file.
private byte[] _bytes = new byte[8_000];
[Benchmark]
public async Task Write100MBAsync()
{
using FileStream fs = new("file.txt", FileMode.Create, FileAccess.Write, FileShare.None, 1, FileOptions.Asynchronous);
for (int i = 0; i < 100_000_000 / 8_000; i++)
await fs.WriteAsync(_bytes);
}
On Windows with an SSD drive, we observed a 4x speedup and more than
a 1200x allocation drop:
Method Runtime Mean Ratio Allocated
Write100MBAsync .NET 5.0 1,308.2 ms 1.00 3,809 KB
Write100MBAsync .NET 6.0 306.8 ms 0.24 3 KB
We also recognized the need for more high-performance file IO
features: concurrent reads and writes, and scatter/gather IO. We
introduced new APIs to the System.IO.File and System.IO.RandomAccess
classes for those cases.
async Task AllOrNothingAsync(string path, IReadOnlyList> buffers)
{
using SafeFileHandle handle = File.OpenHandle(
path, FileMode.Create, FileAccess.Write, FileShare.None, FileOptions.Asynchronous,
preallocationSize: buffers.Sum(buffer => buffer.Length)); // hint for the OS to pre-allocate disk space
await RandomAccess.WriteAsync(handle, buffers, fileOffset: 0); // on Linux it's translated to a single sys-call!
}
The sample demonstrates:
* Opening a file handle using the new File.OpenHandle API.
* Pre-allocating disk space using the new Preallocation Size
feature.
* Writing to the file using the new Scatter/Gather IO API.
The Preallocation Size feature improves performance since write
operations don't need to extend the file and it's less likely that
the file is going to be fragmented. This approach improves
reliability since write operations will no longer fail due to running
out of space since the space has already been reserved. The Scatter/
Gather IO API reduces the number of sys-calls required to write the
data.
Faster interface checking and casting
Interface casting performance has been boosted by 16% - 38%. This
improvement is particularly useful for C#'s pattern matching to and
between interfaces.
image
This chart demonstrates the scale of the improvement for a
representative benchmark.
One of the biggest advantages of moving parts of the .NET runtime
from C++ to managed C# is that it lowers the barrier to contribution.
This includes interface casting, which was moved to C# as an early
.NET 6 change. Many more people in the .NET ecosystem are literate in
C# than C++ (and the runtime uses challenging C++ patterns). Just
being able to read some of the code that composes the runtime is a
major step to developing confidence in contributing in its various
forms.
Credit to Ben Adams.
System.Text.Json Source Generators
We added a source generator for System.Text.Json that avoids the need
for reflection and code generation at runtime, and that enables
generating optimal serialization code at build time. Serializers are
typically written with very conservative techniques because they have
to be. However, if you read your own serialization source code (that
uses a serializer), you can see what the obvious choices should be
that can make a serializer a lot more optimal in your specific case.
That's exactly what this new source generator does.
In addition to increasing performance and reducing memory, the source
generator produces code that is optimal for assembly trimming. That
can help with producing smaller apps.
Serializing POCOs is a very common scenario. Using the new source
generator, we observe that serialization is ~1.6x faster with our
benchmark.
Method Mean StdDev Ratio
Serializer 243.1 ns 9.54 ns 1.00
SrcGenSerializer 149.3 ns 1.91 ns 0.62
The TechEmpower caching benchmark exercises a platform or framework's
in-memory caching of information sourced from a database. The .NET
implementation of the benchmark performs JSON serialization of the
cached data in order to send it as a response to the test harness.
Requests/sec Requests
net5.0 243,000 3,669,151
net6.0 260,928 3,939,804
net6.0 + JSON source gen 364,224 5,499,468
We observe an ~100K RPS gain (~40% increase). .NET 6 scores a 50%
higher throughput than .NET 5 when combined with the MemoryCache
performance improvements!
C# 10
Welcome to C# 10. A major theme of C# 10 is continuing the
simplification journey that started with top-level statements in C# 9
. The new features remove even more ceremony from Program.cs,
resulting in programs as short as a single line. They were inspired
by talking to people -- students, professional developers, and others
-- with no prior C# experience and learning what works best and is
intuitive for them.
Most of the .NET SDK templates have been updated to deliver the much
simpler and more terse experience that is now possible with C# 10.
We've heard feedback that some folks don't like the new templates
because they are not intended for experts, remove object orientation,
remove concepts that are important to learn on day one of writing C#,
or encourage writing a whole program in one file. Objectively, none
of these points are true. The new model is equally intended and
equally appropriate for students as professional developers. It is,
however, different from the C-derived model we've had until .NET 6.
There are several other features and improvements in C# 10, including
record structs.
Global using directives
Global using directives let you specify a using directive just once
and have it applied to every file that you compile.
The following examples show the breadth of the syntax:
* global using System;
* global using static System.Console;
* global using Env = System.Environment;
You can put global using statements in any .cs file, including in
Program.cs.
Implicit usings is an MSBuild concept that automatically adds a set
of global using directives depending on the SDK. For example, console
app implicit usings differ from ASP.NET Core.
Implicit usings are opt-in, and enabled in a PropertyGroup:
* enable
Implicit usings are opt-in for existing projects but included by
default for new C# projects. For more information, see Implicit
usings.
File-scoped namespaces
File-scoped namespaces enable you to declare the namespace for a
whole file without nesting the remaining contents in { ... }. Only
one is allowed, and it must come before any types are declared.
The new syntax is a single line:
namespace MyNamespace;
class MyClass { ... } // Not indented
This new syntax is an alternative to the three-lined indented style:
namespace MyNamespace
{
class MyClass { ... } // Everything is indented
}
The benefit is a reduction indentation in the extremely common case
where your whole file is in the same namespace.
Record structs
C# 9 introduced records as a special value-oriented form of classes.
In C# 10 you can also declare records that are structs. Structs in C#
already have value equality, but record structs add an == operator
and an implementation of IEquatable, as well as a value-based
ToString implementation:
public record struct Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
Just like record classes, record structs can be "positional", meaning
that they have a primary constructor which implicitly declares public
members corresponding to the parameters:
public record struct Person(string FirstName, string LastName);
However, unlike record classes, the implicit public members are
mutable auto-implemented properties. This is so that record structs
are a natural grow-up story for tuples. For example, if you have a
return type that is (string FirstName, string LastName) and you want
to grow that up to a named type, you can easily declare the
corresponding positional struct record and maintain the mutable
semantics.
If you want an immutable record with readonly properties, you can
declare the whole record struct readonly (just as you can other
structs):
public readonly record struct Person(string FirstName, string LastName);
C# 10 also supports with expressions not just for record structs but
for all structs, as well as for anonymous types:
var updatedPerson = person with { FirstName = "Mary" };
F# 6
F# 6 is about making F# simpler and more performant. This applies to
the language design, library, and tooling. Our goal with F# 6 (and
beyond) was to remove corner-cases in the language that surprise
users or present hurdles to learning F#. We are very pleased to have
worked with the F# community in this ongoing effort.
Making F# faster and more interoperable
The new task {...} syntax directly creates a task and starts it. This
is one of the most significant features in F# 6, making asynchronous
tasks simpler, more performant and more interoperable with C# and
other .NET languages. Previously, creating .NET tasks required using
async {...} to create a task and invoking Async.StartImmediateAsTask.
The task {...} feature is built on a foundation called "resumable code"
RFC FS-1087. Resumable code is a core feature, and we expect to use
it to build other high-performance asynchronous and yielding state
machines in the future.
F# 6 also adds other performance features for library authors
including InlineIfLambda and unboxed representations for F# active
patterns. A particularly significant performance improvement is in
the compilation of list and array expressions, which are now up to 4x
faster and have better and simpler debugging as well.
Making F# easier to learn and more uniform
F# 6 enables the expr[idx] indexing syntax. Up until now, F# has used
expr.[idx] for indexing. Dropping the dot-notation is based on
repeated feedback from first-time F# users, that the use of dot comes
across as an unnecessary divergence from the standard practice they
expect. In new code, we recommend the systematic use of the new expr
[idx] indexing syntax. We should all switch to this syntax as a
community.
The F# community has contributed key improvements to make the F#
language more uniform in F# 6. The most important of these is
removing a number of inconsistencies and limitations in F#'s
indentation rules. Other design additions to make F# more uniform
include the addition of as patterns; allowing "overloaded custom
operations" in computation expression (useful for DSLs); allowing _
discards on use bindings and allowing %B for binary formatting in
output. The F# core library adds new functions for copy-and-update on
lists, arrays, and sequences, plus additional NativePtr intrinsics.
Some legacy features of F# deprecated since 2.0 now result in errors.
Many of these changes better align F# with your expectations,
resulting in fewer surprises.
F# 6 also added support for additional "implicit" and "type-directed"
conversions in F#. This means fewer explicit upcasts, and adds
first-class support for .NET-style implicit conversions. F# is also
adjusted to be better suited to the era of numeric libraries using
64-bit integers, with implicit widening for 32-bit integers.
Improving the F# tooling
Tooling improvements in F# 6 make day to day coding easier. New
"pipeline debugging" allows you to step, set breakpoints and inspect
intermediate values for the F# piping syntax input |> f1 |> f2. The
debug display of shadowed values has been improved, eliminating a
common source of confusion when debugging. F# tooling is now also
more performant with the F# compiler performing the parsing stage in
parallel. F# IDE tooling is also improved. F# scripting is now even
more robust, allowing you to pin the version of the .NET SDK used
through global.json files.
Hot Reload
Hot Reload is another performance feature, focused on developer
productivity. It enables you to make a wide variety of code edits to
a running application, collapsing the time you need to spend waiting
for apps to rebuild, restart, or to re-navigate to the same spot
where you were after making a code change.
Hot Reload is available through both the dotnet watch CLI tool and
Visual Studio 2022. You can use Hot Reload with a large variety of
app types such as ASP.NET Core, Blazor, .NET MAUI, Console, Windows
Forms (WinForms), WPF, WinUI 3, Azure Functions, and others.
When using the CLI, simply start your .NET 6 app using dotnet watch,
make any supported edit, and when saving the file (like in Visual
Studio Code) those changes will be immediately applied. If the
changes are not supported, the details will be logged to the command
window.
Hot Reload CLI experience
This image displays an MVC app being launched with dotnet watch. I
made edits to both .cs and .cshtml files (as reported in the log) and
both were applied to the code and reflected in the browser very
quickly, in less than half a second.
When using Visual Studio 2022, simply start your app, make a
supported change, and use the new "Hot Reload" button (displayed in
the following image) to apply those changes. You can also opt to
apply changes on save through the drop-down menu on the same button.
When using Visual Studio 2022, Hot Reload is available for multiple
.NET versions, for .NET 5+, .NET Core, and .NET Framework. For
example, you will be able to make code-behind changes to an
OnClickEvent handler for a button. It is not supported for the Main
method of an application.
Hot Reload Visual Studio 2022 experience
Note: There is a bug in RuntimeInformation.FrameworkDescription that
is demonstrated in that image that will be fixed shortly.
Hot Reload also works in tandem with the existing Edit and Continue
capability (when stopped at a breakpoint), and XAML Hot Reload for
editing an apps UI in real-time. It is currently supported for C# and
Visual Basic apps (not F#).
Security
Security has been significantly improved in .NET 6. It is always an
important focus for the team, including threat modeling,
cryptography, and defense in depth mitigations.
On Linux, we rely on OpenSSL for all cryptographic operations,
including for TLS (required for HTTPS). On macOS and Windows, we rely
on OS-provided functionality for the same purpose. With each new
version of .NET, we often need to add support for a new build of
OpenSSL. .NET 6 adds support for OpenSSL 3.
The biggest changes with OpenSSL 3 are an improved FIPS 140-2 module
and simpler licensing.
.NET 6 requires OpenSSL 1.1 or higher and will prefer the highest
installed version of OpenSSL it can find, up to and including v3. In
the general case, you're most likely to start using OpenSSL 3 when
the Linux distribution you use switches to it as the default. Most
distros have not yet done that. For example, if you install .NET 6 on
Red Hat 8 or Ubuntu 20.04, you will not (at the time of writing)
start using OpenSSL 3.
OpenSSL 3, Windows 10 21H1, and Windows Server 2022 all support
ChaCha20Poly1305. You can use this new authenticated encryption
scheme with .NET 6 (assuming your environment supports it).
Credit to Kevin Jones for Linux support for ChaCha20Poly1305.
We also published a new runtime security mitigation roadmap. It is
important that the runtime you use is safe from textbook attack
types. We're delivering on that need. In .NET 6, we built initial
implementations of W^X and Intel Control-flow enforcement technology
(CET). W^X is fully supported, enabled by default for macOS Arm64,
and is opt-in for other environments. CET is opt-in and a preview for
all environments. We expect both technologies to be enabled by
default for all environments in .NET 7.
Arm64
There is a lot of excitement about Arm64 these days, for laptops,
cloud hardware, and other devices. We feel that same excitement on
the .NET team and are doing our best to keep up with that industry
trend. We partner directly with engineers at Arm Holdings, Apple, and
Microsoft to ensure that our implementations are correct and
optimized, and that our plans align. These close partnerships have
helped us a lot.
* Special thanks to Apple who sent our team a bushel of Arm64 dev
kits to work with prior to the M1 chip launching, and for
significant technical support.
* Special thanks to Arm Holdings, whose engineers code reviewed our
Arm64 changes and also made performance improvements.
We added initial support for Arm64 with .NET Core 3.0 and Arm32
before that. The team has made major investments in Arm64 in each of
the last few releases, and this will continue for the foreseeable
future. In .NET 6, our primary focus was on supporting the new Apple
Silicon chips and the x64 emulation scenario on both macOS and
Windows Arm64 OSes.
You can install both the Arm64 and x64 versions of .NET on macOS 11+
and Windows 11+ Arm64 OSes. We had to make several design choices and
product changes to make sure that worked.
Our strategy is "pro native architecture". We recommend that you
always use the SDK that matches the native architecture, which is the
Arm64 SDK on macOS and Windows Arm64. The SDK is large body of
software. It is going to be much higher performance running natively
on an Arm64 chip than emulated. We've updated the CLI to make that
easy. We're never going to be focused on optimizing emulated x64.
By default, if you dotnet run a .NET 6 app with the Arm64 SDK, it
will run as Arm64. You can easily switch to running as x64 with the
-a argument, like dotnet run -a x64. The same argument works for
other CLI verbs. See .NET 6 RC2 Update for macOS and Windows Arm64
for more information.
There's a subtlety there that I want to ensure is covered. When you
use -a x64, the SDK is still running natively as Arm64. There are
fixed points in the .NET SDK architecture where process boundaries
exist. For the most part, a process must be all Arm64 or all x64. I'm
simplifying a bit, but the .NET CLI waits for the last process
creation in the SDK architecture and launches that one as the chip
architecture you requested, like x64. That's the process your code
runs in. That way, you get the benefit of Arm64 as a developer, but
your code gets to run in the process it needs. This is only relevant
if you need to run some code as x64. If you don't, then you can just
run everything as Arm64 all the time, and that's great.
Arm64 Support
The following are the key points you need to know, for macOS and
Windows Arm64:
* .NET 6 Arm64 and x64 SDKs are supported and recommended.
* All in-support Arm64 and x64 runtimes are supported.
* .NET Core 3.1 and .NET 5 SDKs work but provide less capability
and in some cases are not fully supported.
* dotnet test doesn't yet work correctly with x64 emulation. We are
working on that. dotnet test will be improved as part of the
6.0.200 release, and possibly earlier.
See .NET Support for macOS and Windows Arm64 for more complete
information.
Linux is missing from this discussion. It doesn't support x64
emulation in the same way as macOS and Windows. As a result, these
new CLI features and the support approach don't directly apply to
Linux, nor does Linux need them.
Windows Arm64
We have a simple tool that demonstrates the environment that .NET is
running on.
C:Usersrich>dotnet tool install -g dotnet-runtimeinfo
You can invoke the tool using the following command: dotnet-runtimeinfo
Tool 'dotnet-runtimeinfo' (version '1.0.5') was successfully installed.
C:Usersrich>dotnet runtimeinfo
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP""""""" 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
**.NET information
Version: 6.0.0
FrameworkDescription: .NET 6.0.0-rtm.21522.10
Libraries version: 6.0.0-rtm.21522.10
Libraries hash: 4822e3c3aa77eb82b2fb33c9321f923cf11ddde6
**Environment information
ProcessorCount: 8
OSArchitecture: Arm64
OSDescription: Microsoft Windows 10.0.22494
OSVersion: Microsoft Windows NT 10.0.22494.0
As you can see, the tool is running natively on Windows Arm64. I'll
show you what that looks like ASP.NET Core.
.NET 6 ASP.NET Core running on Windows Arm64, Announcing .NET 6 -- The
Fastest .NET Yet
macOS Arm64
And you can see that the experience is similar on macOS Arm64, with
architecture targeting also demonstrated.
rich@MacBook-Air app % dotnet --version
6.0.100
rich@MacBook-Air app % dotnet --info | grep RID
RID: osx-arm64
rich@MacBook-Air app % cat Program.cs
using System.Runtime.InteropServices;
using static System.Console;
WriteLine($"Hello, {RuntimeInformation.OSArchitecture} from {RuntimeInformation.FrameworkDescription}!");
rich@MacBook-Air app % dotnet run
Hello, Arm64 from .NET 6.0.0-rtm.21522.10!
rich@MacBook-Air app % dotnet run -a x64
Hello, X64 from .NET 6.0.0-rtm.21522.10!
rich@MacBook-Air app %
This image demonstrates that Arm64 execution is the default with the
Arm64 SDK and how easy it is to switch between targeting Arm64 and
x64, using the -a argument. The exact same experience works on
Windows Arm64.
x64 emulation on macOS Arm64 with ASP.NET Core
This image demonstrates the same thing, but with ASP.NET Core. I'm
using the same .NET 6 Arm64 SDK as you saw in the previous image.
Docker on Arm64
Docker supports containers running with native architecture and in
emulation, with native architecture being the default. This seems
obvious but can be confusing when most of the Docker Hub catalog is
x64 oriented. You can use --platform linux/amd64 to request an x64
image.
We only support running Linux Arm64 .NET container images on Arm64
OSes. This is because we've never supported running .NET in QEMU,
which is what Docker uses for architecture emulation. It appears that
this may be due to a limitation in QEMU.
Docker on macOS Arm64
This image demonstrates the console sample we maintain:
mcr.microsoft.com/dotnet/samples. It's a fun sample since it contains
some basic logic for printing CPU and memory limit information that
you can play with. The image I've show sets CPU and memory limits.
Try it for yourself: docker run --rm mcr.microsoft.com/dotnet/samples
Arm64 Performance
The Apple Silicon and x64 emulation support projects were super
important, however, we also improved Arm64 performance generally.
Arm64 performance improvement
This image demonstrates an improvement in zeroing out the contents of
stack frames, which is a common operation. The green line is the new
behavior, while the orange line is another (less beneficial)
experiment, both of which improve relative to the baseline,
represented by the blue line. For this test, lower is better.
Containers
.NET 6 is better for containers, primarily based on all the
improvements discussed in this post, for both Arm64 and x64. We also
made key changes that will help a variety of scenarios. Validate
container improvements with .NET 6 demonstrates some of these
improvements being tested together.
The Windows container improvements and the new environment variable
have also been included in the November .NET Framework 4.8 container
update, releasing November 9th (tomorrow).
Release notes are available at our docker repositories:
*
+ .NET 6 Container Release Notes
+ .NET Framework 4.8 November 2021 Container Release Notes (not
available until 11/9)
Windows Containers
.NET 6 adds support for Windows process-isolated containers. If you
use Windows containers in Azure Kubernetes Service (AKS), then you
are relying on process-isolated containers. Process-isolated
containers can be thought of as very similar to Linux containers.
Linux containers use cgroups and Windows process-isolated containers
use Job Objects. Windows also offer Hyper-V containers, which offers
greater isolation through greater virtualization. There are no
changes in .NET 6 for Hyper-V containers.
The primary value of this change is that Environment.ProcessorCount
will now report the correct value with Windows process-isolated
containers. If you create a 2-core container on a 64-core machine,
Environment.ProcessorCount will return 2. In prior versions, this
property would report the total number of processors on a machine,
independent of the limit specified by the Docker CLI, Kubernetes, or
other container orchestrator/runtime. This value is used by various
parts of .NET for scaling purposes, including the .NET garbage
collector (although it relies on a related, lower-level, API).
Community libraries also rely on this API for scaling.
We recently validated this new capability with a customer, on Windows
Containers in production on AKS using a large set of pods. They were
able to run successfully with 50% memory (compared to their typical
configuration), a level that previously resulted in
OutOfMemoryException and StackOverflowException exceptions. They
didn't take the time to find the minimum memory configuration, but we
guessed it was significantly below 50% of their typical memory
configuration. As a result of this change, they are going to move to
cheaper Azure configurations, saving them money. That's a nice, easy
win, simply by upgrading.
Optimizing scaling
We have heard from users that some applications cannot achieve
optimal scaling when Environment.ProcessorCount reports the correct
value. If this sounds like the opposite of what you just read for
Windows Containers, it kinda-sorta is. .NET 6 now offers the
DOTNET_PROCESSOR_COUNT environment variable to control the value of
Environment.ProcessorCount manually. In the typical use case, an
application might be configured with 4 cores on a 64-core machine,
and scale best in terms of 8 or 16 cores. This environment variable
can be used to enable that scaling.
This model might seem strange, where Environment.ProcessorCount and
--cpus (via the Docker CLI) values can differ. Container runtimes, by
default, are oriented in terms of core equivalents, not actual cores.
That means, when you say you want 4 cores, you get the equivalent CPU
time of 4 cores, but your app might (in theory) run on many more
cores, even all 64 cores on a 64-core machine for a short period.
That may enable your app to scale better on more than 4 threads
(continuing with the example), and allocating more could be
beneficial. This assumes that the thread allocation is based on the
value of Environment.ProcessorCount. If you opt to set a higher
value, it is likely that your app will use more memory. For some
workloads, that is an easy tradeoff. At the very least, it is a new
option you can test.
This new feature is supported for both Linux and Windows Containers.
Docker also offers a CPU groups feature, where your app is
affinitized to specific cores. This feature is not recommended in
that scenario since the number of cores an app has access to is
concretely defined. We have also seen some issues with using it with
Hyper-V containers, and it isn't really intended for that isolation
mode.
Debian 11 "bullseye"
We watch Linux distros lifecycle and release plans very closely and
try to make the best choices on your behalf. Debian is the Linux
distribution we use for our default Linux images. If you pull the 6.0
tag from one of our container repos, you will pull a Debian image
(assuming you are using Linux containers). With each new .NET
release, we consider whether we should adopt a new Debian version.
As a matter of policy, we do not change the Debian version for our
convenience tags, like 6.0, mid-release. If we did, some apps would
be certain to break. That means, that choosing the Debian version at
the start of the release is very important. Also, these images get a
lot of use, mostly because they are references by the "good tags".
The Debian and .NET releases are naturally not planned together. When
we started .NET 6, we saw that Debian "bullseye" would likely be
released in 2021. We decided to take a bet on bullseye from the start
of the release. We started releasing bullseye-based container images
with .NET 6 Preview 1 and decided not to look back. The bet was that
the .NET 6 release would lose the race with the bullseye release. By
August 8th, we still didn't know when bullseye would ship, leaving
three months before our own release would go out, on November 8th. We
didn't want to ship a production .NET 6 on a preview Linux, but we
held firm late to the plan that we'd lose this race.
We were pleasantly surprised when Debian 11 "bullseye" was released
on August 14th. We lost the race but won the bet. That means that
.NET 6 users get the best and latest Debian, by default, from day
one. We believe that Debian 11 and .NET 6 will be a great combination
for a lot of users. Sorry buster, we hit the bullseye.
Newer distro versions include newer major versions of various
packages in their package feed and often get CVE fixes faster. That's
in addition to a newer kernel. Users are better served by a new
distro version.
Looking a little further ahead, we'll start planning support for
Ubuntu 22.04 before long. Ubuntu is another Debian-family distro and
popular with .NET developers. We hope to offer same-day support for
the new Ubuntu LTS release.
Hat tip to Tianon Gravi for maintaining Debian images for the
community and helping us when we have questions.
Dotnet monitor
dotnet monitor is an important diagnostics tool for containers. It
has been available as a sidecar container image for some time, but in
an unsupported "experimental" status. As part of .NET 6, we are
releasing a .NET 6-based dotnet monitor image that is fully-supported
in production.
dotnet monitor is already in use by Azure App Service as an
implementation detail of their ASP.NET Core Linux diagnostics
experience. This is one of the intended scenarios, building on top of
dotnet monitor to provide higher-level and higher-value experiences.
You can pull the new image now:
docker pull mcr.microsoft.com/dotnet/monitor:6.0
dotnet monitor makes it easier to access diagnostic information --
logs, traces, process dumps -- from a .NET process. It is easy to get
access to all the diagnostic information you want on your desktop
machine, however, those same familiar techniques might not work in
production using containers, for example. dotnet monitor provides a
unified way to collect these diagnostic artifacts regardless of
whether running on your desktop machine or in a Kubernetes cluster.
There are two different mechanisms for collection of these diagnostic
artifacts:
* An HTTP API for ad-hoc collection of artifacts. You can call
these API endpoints when you already know your application is
experiencing an issue and you are interested in gathering more
information.
* Triggers for rule-based configuration for always-on collection of
artifacts. You may configure rules to collect diagnostic data
when a desired condition is met, for example, collect a process
dump when you have sustained high CPU.
dotnet monitor provides a common diagnostic API for .NET apps that
works everywhere you want with any tools you want. The "common API"
isn't a .NET API but a web API that you can call and query. dotnet
monitor includes an ASP.NET web server that directly interacts with
and exposes data from a diagnostics server in the .NET runtime. The
design of dotnet monitor enables high-performance monitoring in
production and secure use to gate access to privileged information.
dotnet monitor interacts with the runtime -- across container
boundaries -- via a non-internet-addressable unix domain socket. That
model communication model is a perfect fit for this use case.
Structured JSON logs
The JSON formatter is now the default console logger in the aspnet
.NET 6 container image. The default in .NET 5 was set to the simple
console formatter. This change was made in order to have a default
configuration that works with automated tools that rely on a
machine-readable format like JSON.
The output now looks like the following for the aspnet image:
$ docker run --rm -it -p 8000:80 mcr.microsoft.com/dotnet/samples:aspnetapp
{"EventId":60,"LogLevel":"Warning","Category":"Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository","Message":"Storing keys in a directory u0027/root/.aspnet/DataProtection-Keysu0027 that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.","State":{"Message":"Storing keys in a directory u0027/root/.aspnet/DataProtection-Keysu0027 that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.","path":"/root/.aspnet/DataProtection-Keys","{OriginalFormat}":"Storing keys in a directory u0027{path}u0027 that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed."}}
{"EventId":35,"LogLevel":"Warning","Category":"Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager","Message":"No XML encryptor configured. Key {86cafacf-ab57-434a-b09c-66a929ae4fd7} may be persisted to storage in unencrypted form.","State":{"Message":"No XML encryptor configured. Key {86cafacf-ab57-434a-b09c-66a929ae4fd7} may be persisted to storage in unencrypted form.","KeyId":"86cafacf-ab57-434a-b09c-66a929ae4fd7","{OriginalFormat}":"No XML encryptor configured. Key {KeyId:B} may be persisted to storage in unencrypted form."}}
{"EventId":14,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Now listening on: http://[::]:80","State":{"Message":"Now listening on: http://[::]:80","address":"http://[::]:80","{OriginalFormat}":"Now listening on: {address}"}}
{"EventId":0,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Application started. Press Ctrlu002BC to shut down.","State":{"Message":"Application started. Press Ctrlu002BC to shut down.","{OriginalFormat}":"Application started. Press Ctrlu002BC to shut down."}}
{"EventId":0,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Hosting environment: Production","State":{"Message":"Hosting environment: Production","envName":"Production","{OriginalFormat}":"Hosting environment: {envName}"}}
{"EventId":0,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Content root path: /app","State":{"Message":"Content root path: /app","contentRoot":"/app","{OriginalFormat}":"Content root path: {contentRoot}"}}
The logger format type can be changed by setting or unsetting the
Logging__Console__FormatterName environment variable or via code
change (see Console log formatting for more details).
After changing it, you will see output like the following (just like
.NET 5):
$ docker run --rm -it -p 8000:80 -e Logging__Console__FormatterName="" mcr.microsoft.com/dotnet/samples:aspnetapp
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
No XML encryptor configured. Key {8d4ddd1d-ccfc-4898-9fe1-3e7403bf23a0} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: /app
Note: This change doesn't affect the .NET SDK on your developer
machine, like with dotnet run. This change is specific to the aspnet
container image.
Support for OpenTelemetry Metrics
We've been adding support for OpenTelemetry for the last couple .NET
versions, as part of our focus on observability. In .NET 6, we're
adding support for the OpenTelemetry Metrics API. By adding support
for OpenTelemetry, your apps can seamlessly interoperate with other
OpenTelemetry systems.
System.Diagnostics.Metrics is the .NET implementation of the
OpenTelemetry Metrics API specification. The Metrics APIs are
designed explicitly for processing raw measurements, with the intent
of producing continuous summaries of those measurements, efficiently
and simultaneously.
The APIs include the Meter class which can be used to create
instrument objects. The APIs expose four instrument classes: Counter,
Histogram, ObservableCounter, and ObservableGauge to support
different metrics scenarios. Also, the APIs expose the MeterListener
class to allow listening to the instrument's recorded measurement for
aggregation and grouping purposes.
The OpenTelemetry .NET implementation will be extended to use these
new APIs, which add support for Metrics observability scenarios.
Library Measurement Recording Example
Meter meter = new Meter("io.opentelemetry.contrib.mongodb", "v1.0");
Counter counter = meter.CreateCounter("Requests");
counter.Add(1);
counter.Add(1, KeyValuePair.Create("request", "read"));
Listening Example
MeterListener listener = new MeterListener();
listener.InstrumentPublished = (instrument, meterListener) =>
{
if (instrument.Name == "Requests" && instrument.Meter.Name == "io.opentelemetry.contrib.mongodb")
{
meterListener.EnableMeasurementEvents(instrument, null);
}
};
listener.SetMeasurementEventCallback((instrument, measurement, tags, state) =>
{
Console.WriteLine($"Instrument: {instrument.Name} has recorded the measurement {measurement}");
});
listener.Start();
Windows Forms
We have continued to make key improvements in Windows Forms. .NET 6
includes better accessibility for controls, the ability to set an
application-wide default font, template updates and others.
Accessibility improvements
In this release, we added UIA providers for CheckedListBox,
LinkLabel, Panel, ScrollBar, TabControl and TrackBar that enable
tools like Narrator, and test automation to interact with the
elements of an application.
Default font
You can now set a default font for an application with
Application.SetDefaultFont.
void Application.SetDefaultFont(Font font)
Minimal applications
The following is a minimal Windows Forms application with .NET 6:
class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
}
}
As part of the .NET 6 release, we've been updating most of the
templates to them more modern and minimal, including with Windows
Forms. We decided to keep the Windows Forms template a bit more
traditional, in part because of the need for the [STAThread]
attribute to apply to the application entrypoint. However, there is
more a play than immediately meets the eye.
ApplicationConfiguration.Initialize() is a source generated API that
behind the scenes emits the following calls:
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.SetDefaultFont(new Font(...));
Application.SetHighDpiMode(HighDpiMode.SystemAware);
The parameters of these calls are configurable via MSBuild properties
in csproj or props files.
The Windows Forms designer in Visual Studio 2022 is also aware of
these properties (for now it only reads the default font), and can
show you your application as it would look at runtime:
Application bootstrap, Announcing .NET 6 -- The Fastest .NET Yet
Template updates
Windows Forms templates for C# have been updated to support the new
application bootstrap, global using directives, file-scoped
namespaces, and nullable reference types.
More runtime designers
Now you can build a general-purpose designer (for example, a report
designer) since .NET 6 has all the missing pieces for designers and
designer-related infrastructure. For more information, see this blog
post.
Single-file Apps
In .NET 6, in-memory single file apps have been enabled for Windows
and macOS. In .NET 5, this deployment type was limited to Linux. You
can now publish a single-file binary that is both deployed and
launched as a single file, for all supported OSes. Single files apps
no longer extract any core runtime assemblies to temporary
directories.
This expanded capability is based on a building block called
"superhost". "apphost" is the executable that launches your
application in the non-single-file case, like myapp.exe or ./myapp.
Apphost contains code to find the runtime, load it, and start your
app with that runtime. Superhost still performs some of those tasks
but uses a statically linked copy of all the CoreCLR native binaries.
Static linking is the approach we use to enable the single file
experience.
Native dependencies (like that come with a NuGet package) are the
notable exception to single-file embedding. They are not included in
the single file by default. For instance, WPF native dependencies are
not part of the superhost, resulting in additional files beside the
single file app. You can use the setting
IncludeNativeLibrariesForSelfExtract to embed and extract
native-dependencies.
Static Analysis
We've improved single-file analyzers to allow for custom warnings. If
you have an API which doesn't work in single-file publishing you can
now mark it with the [RequiresAssemblyFiles] attribute, and a warning
will appear if the analyzer is enabled. Adding that attribute will
also silence all warnings related to single-file in the method, so
you can use the warning to propagate warnings upward to your public
API.
The single-file analyzer is automatically enabled for exe projects
when PublishSingleFile is set to true, but you can also enable it for
any project by setting EnableSingleFileAnalysis to true. This is
helpful if you want to support a library being part of a single file
app.
In .NET 5, we added warning for Assembly.Location and a few other
APIs which behave differently in single-file bundles.
Compression
Single-file bundles now support compression, which can be enabled by
setting the property EnableCompressionInSingleFile to true. At
runtime, files are decompressed to memory as necessary. Compression
can provide huge space savings for some scenarios.
Let's look at single file publishing, with and without compression,
used with NuGet Package Explorer.
Without compression: 172 MB
Announcing .NET 6 -- The Fastest .NET Yet
With compression: 71.6 MB
Announcing .NET 6 -- The Fastest .NET Yet
Compression can significantly increase the startup time of the
application, especially on Unix platforms. Unix platforms have a
no-copy fast-start path that can't be used with compression. You
should test your app after enabling compression to see if the
additional startup cost is acceptable.
Single-file debugging
Single-file apps can currently only be debugged using platform
debuggers, like WinDBG. We are looking at adding Visual Studio
debugging with a later build of Visual Studio 2022.
Single-file signing on macOS
Single file apps now satisfy Apple notarization and signing
requirements on macOS. The specific changes relate to the way that we
construct single file apps in terms of discrete file layout.
Apple started enforcing new requirements for signing and notarization
with macOS Catalina. We've been working closely with Apple to
understand the requirements, and to find solutions that enable a
development platform like .NET to work well in that environment.
We've made product changes and documented user workflows to satisfy
Apple requirements in each of the last few .NET releases. One of the
remaining gaps was single-file signing, which is a requirement to
distribute a .NET app on macOS, including in the macOS store.
IL trimming
The team has been working on IL trimming for multiple releases. .NET
6 represents a major step forward on that journey. We've been working
to make a more aggressive trimming mode safe and predictable and as a
result have confidence to make it the default. TrimMode=link was
previously an opt-in feature and is now the default.
We've had a three-pronged strategy to trimming:
* Improve trim-ability of the platform.
* Annotate the platform to provide better warnings and to enable
others to do the same.
* Based on that, make the default trim mode more aggressive so that
it is easy to make apps small.
Trimming has previously been in preview because of the unreliable
results for apps which use unannotated reflection. With trim
warnings, the experience should now be predictable. Apps without trim
warnings should trim correctly and observe no change in behavior when
running. Currently, only the core .NET libraries have been fully
annotated for trimming, but we hope to see the ecosystem annotate for
trimming and become trim compatible
Reducing app size
Let's take a look at this trimming improvement using crossgen, which
is one of the SDK tools. It can be trimmed with only a few trim
warnings, which the crossgen team was able to resolve.
First, let's look at publishing crossgen as a self-contained app
without trimming. It is 80 MB (which includes the .NET runtime and
all the libraries).
image
We can then try out the (now legacy) .NET 5 default trim mode,
copyused. The result drops to 55 MB.
image
The new .NET 6 default trim mode, link, drops the self-contained file
size much further, to 36MB.
image
We hope that the new link trim mode aligns much better with the
expectations for trimming: significant savings and predictable
results.
Warnings enabled by default
Trim warnings tell you about places where trimming may remove code
that's used at runtime. These warnings were previously disabled by
default because the warnings were very noisy, largely due to the .NET
platform not participating in trimming as a first class scenario.
We've annotated large portions of the .NET libraries so that they
produce accurate trim warnings. As a result, we felt it was time to
enable trimming warnings by default. The ASP.NET Core and Windows
Desktop runtime libraries have not been annotated. We plan to
annotate ASP.NET service components next (after .NET 6). We're hoping
to see the community annotate NuGet libraries now that .NET 6 is
released.
You can disable warnings by setting to
true.
More information:
* Trim warnings
* Intro to trimming
* Prepare .NET libraries for trimming
Shared with Native AOT
We've implemented the same trimming warnings for the Native AOT
experiment as well, which should improve the Native AOT compilation
experience in much the same way.
Math
We've improved Math APIs significantly. Some folks in the community
have been enjoying these improvements already.
Performance-oriented APIs
Performance-oriented math APIs have been added to System.Math. Their
implementation is hardware accelerated if the underlying hardware
supports it.
New APIs:
* SinCos for simultaneously computing Sin and Cos.
* ReciprocalEstimate for computing an approximate of 1 / x.
* ReciprocalSqrtEstimate for computing an approximate of 1 / Sqrt
(x).
New overloads:
* Clamp, DivRem, Min, and Max supporting nint and nuint.
* Abs and Sign supporting nint.
* DivRem variants which return a tuple.
Perf improvements:
* ScaleB was ported to C# resulting in calls being up to 93% faster
. Credit to Alex Covington.
BigInteger Performance
Parsing of BigIntegers from both decimal and hexadecimal strings has
been improved. We see improvements of up to 89%, as demonstrated in
the following chart (lower is better).
graph, Announcing .NET 6 -- The Fastest .NET Yet
Credit to Joseph Da Silva.
Complex APIs now annotated as readonly
Various System.Numerics.Complex APIs are now annotated as readonly to
ensure that no copy is made for readonly values or values passed by
in.
Credit to hrrrrustic.
BitConverter now supports floating-point to unsigned integer bitcasts
BitConverter now supports DoubleToUInt64Bits, HalfToUInt16Bits,
SingleToUInt32Bits, UInt16BitsToHalf, UInt32BitsToSingle, and
UInt64BitsToDouble. This should make it easier to do floating-point
bit manipulation when required.
Credit to Michal Petryka.
BitOperations supports additional functionality
BitOperations now supports IsPow2, RoundUpToPowerOf2, and provides
nint/nuint overloads for existing functions.
Credit to John Kelly, Huo Yaoyuan, and Robin Lindner.
Vector, Vector2, Vector3, and Vector4 improvements
Vector now supports the nint and nuint primitive types, added in C
# 9. For example, this change should make it simpler to use SIMD
instructions with pointers or platform-dependent length types.
Vector now supports a Sum method to simplify needing to compute
the "horizontal sum" of all elements in the vector. Credit to Ivan
Zlatanov.
Vector now supports a generic As method to simplify
dealing with vectors in generic contexts where the concrete type
isn't known. Credit to Huo Yaoyuan
Overloads supporting Span were added to Vector2, Vector3, and
Vector4 to improve the experience when needing to load or store
vector types.
Better parsing of standard numeric formats
We've improved the parser for the standard numeric types,
specifically for .ToString and .TryFormat. They will now understand
requests for precision > 99 decimal places and will provide accurate
results to that many digits. Also, the parser now better supports
trailing zeros in the Parse method.
The following examples demonstrate before and after behavior.
* 32.ToString("C100") -> C132
+ .NET 6:
$32.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ .NET 5: We had an artificial limitation in the formatting
code to only handle a precision of <= 99. For precision >=
100, we instead interpreted the input as a custom format.
* 32.ToString("H99") -> throw a FormatException
+ .NET 6: throws a FormatException
+ This is correct behavior, but it's called here to contrast
with the next example.
* 32.ToString("H100") -> H132
+ .NET 6: throw a FormatException
+ .NET 5: H is an invalid format specifier. So, we should've
thrown a FormatException. Instead, our incorrect behavior of
interpreting precision >= 100 as custom formats meant we
returned wrong values.
* double.Parse("9007199254740997.0") -> 9007199254740998
+ .NET 6: 9007199254740996.
+ .NET 5: 9007199254740997.0 is not exactly representable in
the IEEE 754 format. With our current rounding scheme, the
correct return value should have been 9007199254740996.
However, the last .0 portion of the input was forcing the
parser to incorrectly round the result and return
9007199254740998.
System.Text.Json
System.Text.Json provides multiple high-performance APIs for
processing JSON documents. Over the last few releases, we've added
new functionality to further improve JSON processing performance and
to mitigate blockers for people wanting to migrate from
NewtonSoft.Json. This release includes continues on that path and is
major step forward on performance, particularly with the serializer
source generator.
JsonSerializer source generation
Note: Apps that used source generation with .NET 6 RC1 or earlier
builds should be re-compiled.
The backbone of nearly all .NET serializers is reflection. Reflection
is a great capability for certain scenarios, but not as the basis of
high-performance cloud-native applications (which typically (de)
serialize and process a lot of JSON documents). Reflection is a
problem for startup, memory usage, and assembly trimming.
The alternative to runtime reflection is compile-time source
generation. In .NET 6, we are including a new source generator as
part of System.Text.Json. The JSON source generator works in
conjunction with JsonSerializer and can be configured in multiple
ways.
It can provide the following benefits:
* Reduce start-up time
* Improve serialization throughput
* Reduce private memory usage
* Remove runtime use of System.Reflection and
System.Reflection.Emit
* IL trimming compatibility
By default, the JSON source generator emits serialization logic for
the given serializable types. This delivers higher performance than
using the existing JsonSerializer methods by generating source code
that uses Utf8JsonWriter directly. In short, source generators offer
a way of giving you a different implementation at compile-time in
order to make the runtime experience better.
Given a simple type:
namespace Test
{
internal class JsonMessage
{
public string Message { get; set; }
}
}
The source generator can be configured to generate serialization
logic for instances of the example JsonMessage type. Note that the
class name JsonContext is arbitrary. You can use whichever class name
you want for the generated source.
using System.Text.Json.Serialization;
namespace Test
{
[JsonSerializable(typeof(JsonMessage)]
internal partial class JsonContext : JsonSerializerContext
{
}
}
The serializer invocation with this mode could look like the
following example. This example provides the best possible
performance.
using MemoryStream ms = new();
using Utf8JsonWriter writer = new(ms);
JsonSerializer.Serialize(jsonMessage, JsonContext.Default.JsonMessage);
writer.Flush();
// Writer contains:
// {"Message":"Hello, world!"}
The fastest and most optimized source generation mode -- based on
Utf8JsonWriter -- is currently only available for serialization.
Similar support for deserialization -- based on Utf8JsonReader -- may
be provided in the future depending on your feedback.
The source generator also emits type-metadata initialization logic
that can benefit deserialization as well. To deserialize an instance
of JsonMessage using pre-generated type metadata, you can do the
following:
JsonSerializer.Deserialize(json, JsonContext.Default.JsonMessage);
JsonSerializer support for IAsyncEnumerable
You can now (de)serialize IAsyncEnumerable JSON arrays with
System.Text.Json.The following examples use streams as a
representation of any async source of data. The source could be files
on a local machine, or results from a database query or web service
API call.
JsonSerializer.SerializeAsync has been updated to recognize and
provide special handing for IAsyncEnumerable values.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
static async IAsyncEnumerable PrintNumbers(int n)
{
for (int i = 0; i < n; i++) yield return i;
}
using Stream stream = Console.OpenStandardOutput();
var data = new { Data = PrintNumbers(3) };
await JsonSerializer.SerializeAsync(stream, data); // prints {"Data":[0,1,2]}
IAsyncEnumerable values are only supported using the asynchronous
serialization methods. Attempting to serialize using the synchronous
methods will result in a NotSupportedException being thrown.
Streaming deserialization required a new API that returns
IAsyncEnumerable. We added the
JsonSerializer.DeserializeAsyncEnumerable method for this purpose, as
you can see in the following example.
using System;
using System.IO;
using System.Text;
using System.Text.Json;
var stream = new MemoryStream(Encoding.UTF8.GetBytes("[0,1,2,3,4]"));
await foreach (int item in JsonSerializer.DeserializeAsyncEnumerable(stream))
{
Console.WriteLine(item);
}
This example will deserialize elements on-demand and can be useful
when consuming particularly large data streams. It only supports
reading from root-level JSON arrays, although that could potentially
be relaxed in the future based on feedback.
The existing DeserializeAsync method nominally supports
IAsyncEnumerable, but within the confines of its non-streaming
method signature. It must return the final result as a single value,
as you can see in the following example.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;
var stream = new MemoryStream(Encoding.UTF8.GetBytes(@"{""Data"":[0,1,2,3,4]}"));
var result = await JsonSerializer.DeserializeAsync(stream);
await foreach (int item in result.Data)
{
Console.WriteLine(item);
}
public class MyPoco
{
public IAsyncEnumerable Data { get; set; }
}
In this example, the deserializer will have buffered all
IAsyncEnumerable contents in memory before returning the deserialized
object. This is because the deserializer needs to have consumed the
entire JSON value before returning a result.
System.Text.Json: Writable DOM Feature
The writeable JSON DOM feature adds a new straightforward and
high-performance programming model for System.Text.Json. This new API
is attractive since it avoids needing strongly-typed serialization
contracts, and the DOM is mutable as opposed to the existing
JsonDocument type.
This new API has the following benefits:
* A lightweight alternative to serialization for cases when using
POCO types is not possible or desired, or when a JSON schema is
not fixed and must be inspected.
* Enables efficient modification of a subset of a large tree. For
example, it is possible to efficiently navigate to a subsection
of a large JSON tree and read an array or deserialize a POCO from
that subsection. LINQ can also be used with that.
The following example demonstrates the new programming model.
// Parse a JSON object
JsonNode jNode = JsonNode.Parse("{"MyProperty":42}");
int value = (int)jNode["MyProperty"];
Debug.Assert(value == 42);
// or
value = jNode["MyProperty"].GetValue();
Debug.Assert(value == 42);
// Parse a JSON array
jNode = JsonNode.Parse("[10,11,12]");
value = (int)jNode[1];
Debug.Assert(value == 11);
// or
value = jNode[1].GetValue();
Debug.Assert(value == 11);
// Create a new JsonObject using object initializers and array params
var jObject = new JsonObject
{
["MyChildObject"] = new JsonObject
{
["MyProperty"] = "Hello",
["MyArray"] = new JsonArray(10, 11, 12)
}
};
// Obtain the JSON from the new JsonObject
string json = jObject.ToJsonString();
Console.WriteLine(json); // {"MyChildObject":{"MyProperty":"Hello","MyArray":[10,11,12]}}
// Indexers for property names and array elements are supported and can be chained
Debug.Assert(jObject["MyChildObject"]["MyArray"][1].GetValue() == 11);
ReferenceHandler.IgnoreCycles
JsonSerializer (System.Text.Json) now supports the ability to ignore
cycles when serializing an object graph. The
ReferenceHandler.IgnoreCycles option has similar behavior as
Newtonsoft.Json ReferenceLoopHandling.Ignore. One key difference is
that the System.Text.Json implementation replaces reference loops
with the null JSON token instead of ignoring the object reference.
You can see the behavior of ReferenceHandler.IgnoreCycles in the
following example. In this case, the Next property is serialized as
null since it otherwise creates a cycle.
class Node
{
public string Description { get; set; }
public object Next { get; set; }
}
void Test()
{
var node = new Node { Description = "Node 1" };
node.Next = node;
var opts = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.IgnoreCycles };
string json = JsonSerializer.Serialize(node, opts);
Console.WriteLine(json); // Prints {"Description":"Node 1","Next":null}
}
Source build
With source build, you can build the .NET SDK on your own machine
from source with just a few commands. Let me explain why this project
is important.
Source build is a scenario and also infrastructure that we've been
working on in collaboration with Red Hat since before shipping .NET
Core 1.0. Several years later, we're very close to delivering a fully
automated version of it. For Red Hat Enterprise Linux (RHEL) .NET
users, this capability is a big deal. Red Hat tells us that .NET has
grown to become an important developer platform for their ecosystem.
Nice!
The gold standard for Linux distros is to build open source code
using compilers and toolchains that are part of the distro archive.
That works for the .NET runtime (written in C++), but not for any of
the code written in C#. For C# code, we use a two-pass build
mechanism to satisfy distro requirements. It's a bit complicated, but
it's important to understand the flow.
Red Hat builds .NET SDK source using the Microsoft binary build of
the .NET SDK (#1) to produce a pure open source binary build of the
SDK (#2). After that, the same SDK source code is built again using
this fresh build of the SDK (#2) to produce a provably open source
SDK (#3). This final binary build of the .NET SDK (#3) is then made
available to RHEL users. After that, Red Hat can use this same SDK (#
3) to build new .NET versions and no longer needs to use the
Microsoft SDK to build monthly updates.
That process may be surprising and confusing. Open source distros
need to be built by open source tools. This pattern ensures that the
Microsoft build of the SDK isn't required, either by intention or
accident. There is a higher bar, as a developer platform, to being
included in a distro than just using a compatible license. The source
build project has enabled .NET to meet that bar.
The deliverable for source build is a source tarball. The source
tarball contains all the source for the SDK (for a given release).
From there, Red Hat (or another organization) can build their own
version of the SDK. Red Hat policy requires using a built-from-source
toolchain to produce a binary tar ball, which is why they use a
two-pass methodology. But this two-pass method is not required for
source build itself.
It is common in the Linux ecosystem to have both source and binary
packages or tarballs available for a given component. We already had
binary tarballs available and now have source tarballs as well. That
makes .NET match the standard component pattern.
The big improvement in .NET 6 is that the source tarball is a now a
product of our build. It used to require significant manual effort to
produce, which also resulted in significant latency delivering the
source tarball to Red Hat. Neither party was happy about that.
We've been working closely with Red Hat on this project for five+
years. It has succeeded, in no small part, due to the efforts of the
excellent Red Hat engineers we've had the pleasure of working with.
Other distros and organizations have and will benefit from their
efforts.
As a side note, source build is a big step towards reproducible
builds, which we also strongly believe in. The .NET SDK and C#
compiler have significant reproducible build capabilities.
Libraries APIs
The following APIs have been added, in the addition to the ones
already covered.
WebSocket Compression
Compression is important for any data transmitted over a network.
WebSockets now enable compression. We used an implementation of
permessage-deflate extension for WebSockets, RFC 7692. It allows
compressing WebSockets message payloads using the DEFLATE algorithm.
This feature was one of the top user requests for Networking on
GitHub.
Compression used with encryption may lead to attacks, like CRIME and
BREACH. It means that a secret cannot be sent together with
user-generated data in a single compression context, otherwise that
secret could be extracted. To bring a user's attention to these
implications and help them weigh the risks, we named one of the key
APIs DangerousDeflateOptions. We also added the ability to turn off
compression for specific messages, so if the user would want to send
a secret, they could do that securely without compression.
The memory footprint of the WebSocket when compression is disabled
was reduced by about 27%.
Enabling the compression from the client side is easy, as you can see
in the following example. However, bear in mind that the server can
negotiate the settings, such as, requesting a smaller window or
denying compression completely.
var cws = new ClientWebSocket();
cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions()
{
ClientMaxWindowBits = 10,
ServerMaxWindowBits = 10
};
WebSocket compression support for ASP.NET Core was also added.
Credit to Ivan Zlatanov.
Socks proxy support
SOCKS is a proxy server implementation that can process any TCP or
UDP traffic, making it a very versatile system. It is a long-standing
community request that has been added to .NET 6.
This change adds support for Socks4, Socks4a, and Socks5. For
example, it enables testing external connections via SSH or
connecting to the Tor network.
The WebProxy class now accepts socks schemes, as you can see in the
following example.
var handler = new HttpClientHandler
{
Proxy = new WebProxy("socks5://127.0.0.1", 9050)
};
var httpClient = new HttpClient(handler);
Credit to Huo Yaoyuan.
Microsoft.Extensions.Hosting -- ConfigureHostOptions API
We added a new ConfigureHostOptions API on IHostBuilder to make
application setup simpler (for example, configuring the shutdown
timeout):
using HostBuilder host = new()
.ConfigureHostOptions(o =>
{
o.ShutdownTimeout = TimeSpan.FromMinutes(10);
})
.Build();
host.Run();
In .NET 5, configuring the host options was a bit more complicated:
using HostBuilder host = new()
.ConfigureServices(services =>
{
services.Configure(o =>
{
o.ShutdownTimeout = TimeSpan.FromMinutes(10);
});
})
.Build();
host.Run();
Microsoft.Extensions.DependencyInjection -- CreateAsyncScope APIs
The CreateAsyncScope API was created to handle disposal of
IAsyncDisposable services. Previously, you might have noticed that
disposal of a IAsyncDisposable service provider could throw an
InvalidOperationException exception.
The following example demonstrates the new pattern, using
CreateAsyncScope to enable safe use of the using statement.
await using (var scope = provider.CreateAsyncScope())
{
var foo = scope.ServiceProvider.GetRequiredService();
}
The following example demonstrate the existing problem case:
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
await using var provider = new ServiceCollection()
.AddScoped()
.BuildServiceProvider();
// This using can throw InvalidOperationException
using (var scope = provider.CreateScope())
{
var foo = scope.ServiceProvider.GetRequiredService();
}
class Foo : IAsyncDisposable
{
public ValueTask DisposeAsync() => default;
}
The following pattern was the previous suggested workaround to avoid
the exception. It is no longer required.
var scope = provider.CreateScope();
var foo = scope.ServiceProvider.GetRequiredService();
await ((IAsyncDisposable)scope).DisposeAsync();
Credit to Martin Bjorkstrom.
Microsoft.Extensions.Logging -- compile-time source generator
.NET 6 introduces the LoggerMessageAttribute type. This attribute is
part of the Microsoft.Extensions.Logging namespace, and when used, it
source-generates performant logging APIs. The source-generation
logging support is designed to deliver a highly usable and highly
performant logging solution for modern .NET applications. The
auto-generated source code relies on the ILogger interface in
conjunction with LoggerMessage.Define functionality.
The source generator is triggered when the LoggerMessageAttribute is
used on partial logging methods. When triggered, it is either able to
autogenerate the implementation of the partial methods it's
decorating or produce compile-time diagnostics with hints about
proper usage. The compile-time logging solution is typically
considerably faster at runtime than existing logging approaches. It
achieves this by eliminating boxing, temporary allocations, and
copies to the maximum extent possible.
There are benefits over manually using LoggerMessage.Define APIs
directly:
* Shorter and simpler syntax: Declarative attribute usage rather
than coding boilerplate.
* Guided developer experience: The generator gives warnings to help
developers do the right thing.
* Support for an arbitrary number of logging parameters.
LoggerMessage.Define supports a maximum of six.
* Support for dynamic log level. This is not possible with
LoggerMessage.Define alone.
To use the LoggerMessageAttribute, the consuming class and method
need to be partial. The code generator is triggered at compile time
and generates an implementation of the partial method.
public static partial class Log
{
[LoggerMessage(EventId = 0, Level = LogLevel.Critical, Message = "Could not open socket to `{hostName}`")]
public static partial void CouldNotOpenSocket(ILogger logger, string hostName);
}
In the preceding example, the logging method is static and the log
level is specified in the attribute definition. When using the
attribute in a static context, the ILogger instance is required as a
parameter. You may choose to use the attribute in a non-static
context as well. For more examples and usage scenarios, visit the
compile-time logging source generator documentation.
System.Linq -- Enumerable support for Index and Range parameters
The Enumerable.ElementAt method now accepts indices from the end of
the enumerable, as you can see in the following example.
Enumerable.Range(1, 10).ElementAt(^2); // returns 9
An Enumerable.Take overload has been added that accepts Range
parameters. It simplifies taking slices of enumerable sequences:
* source.Take(..3) instead of source.Take(3)
* source.Take(3..) instead of source.Skip(3)
* source.Take(2..7) instead of source.Take(7).Skip(2)
* source.Take(^3..) instead of source.TakeLast(3)
* source.Take(..^3) instead of source.SkipLast(3)
* source.Take(^7..^3) instead of source.TakeLast(7).SkipLast(3).
Credit to @dixin.
System.Linq -- TryGetNonEnumeratedCount
The TryGetNonEnumeratedCount method attempts to obtain the count of
the source enumerable without forcing an enumeration. This approach
can be useful in scenarios where it is useful to preallocate buffers
ahead of enumeration, as you can see in the following example.
List buffer = source.TryGetNonEnumeratedCount(out int count) ? new List(capacity: count) : new List();
foreach (T item in source)
{
buffer.Add(item);
}
TryGetNonEnumeratedCount checks for sources implementing ICollection/
ICollection or takes advantage of some of the internal
optimizations employed by Linq.
System.Linq -- DistinctBy/UnionBy/IntersectBy/ExceptBy
New variants have been added to the set operations that allow
specifying equality using key selector functions, as you can see in
the following example.
Enumerable.Range(1, 20).DistinctBy(x => x % 3); // {1, 2, 3}
var first = new (string Name, int Age)[] { ("Francis", 20), ("Lindsey", 30), ("Ashley", 40) };
var second = new (string Name, int Age)[] { ("Claire", 30), ("Pat", 30), ("Drew", 33) };
first.UnionBy(second, person => person.Age); // { ("Francis", 20), ("Lindsey", 30), ("Ashley", 40), ("Drew", 33) }
System.Linq -- MaxBy/MinBy
MaxBy and MinBy methods allow finding maximal or minimal elements
using a key selector, as you can see in the following example.
var people = new (string Name, int Age)[] { ("Francis", 20), ("Lindsey", 30), ("Ashley", 40) };
people.MaxBy(person => person.Age); // ("Ashley", 40)
System.Linq -- Chunk
Chunk can be used to chunk a source enumerable into slices of a fixed
size, as you can see in the following example.
IEnumerable chunks = Enumerable.Range(0, 10).Chunk(size: 3); // { {0,1,2}, {3,4,5}, {6,7,8}, {9} }
Credit to Robert Andersson.
System.Linq -- FirstOrDefault/LastOrDefault/SingleOrDefault overloads
taking default parameters
The existing FirstOrDefault/LastOrDefault/SingleOrDefault methods
return default(T) if the source enumerable is empty. New overloads
have been added that accept a default parameter to be returned in
that case, as you can see in the following example.
Enumerable.Empty().SingleOrDefault(-1); // returns -1
Credit to @Foxtrek64.
System.Linq -- Zip overload accepting three enumerables
The Zip method now supports combining three enumerables, as you can
see in the following example.
var xs = Enumerable.Range(1, 10);
var ys = xs.Select(x => x.ToString());
var zs = xs.Select(x => x % 2 == 0);
foreach ((int x, string y, bool z) in Enumerable.Zip(xs,ys,zs))
{
}
Credit to Huo Yaoyuan.
PriorityQueue
PriorityQueue (System.Collections.Generic) is a
new collection that enables adding new items with a value and a
priority. On dequeue the PriorityQueue returns the element with the
lowest priority value. You can think of this new collection as
similar to Queue but that each enqueued element has a priority
value that affects the behavior of dequeue.
The following sample demonstrates the behavior of PriorityQueue
.
// creates a priority queue of strings with integer priorities
var pq = new PriorityQueue();
// enqueue elements with associated priorities
pq.Enqueue("A", 3);
pq.Enqueue("B", 1);
pq.Enqueue("C", 2);
pq.Enqueue("D", 3);
pq.Dequeue(); // returns "B"
pq.Dequeue(); // returns "C"
pq.Dequeue(); // either "A" or "D", stability is not guaranteed.
Credit to Patryk Golebiowski.
Faster handling of structs as Dictionary values
CollectionsMarshal.GetValueRef is a new unsafe API that makes
updating struct values in Dictionaries faster. The new API is
intended for high performance scenarios, not for general purpose use.
It returns a ref to the struct value, which can then be updated in
place with typical techniques.
The following example demonstrates using the new API:
ref MyStruct value = CollectionsMarshal.GetValueRef(dictionary, key);
// Returns Unsafe.NullRef() if it doesn't exist; check using Unsafe.IsNullRef(ref value)
if (!Unsafe.IsNullRef(ref value))
{
// Mutate in-place
value.MyInt++;
}
Prior to this change, updating struct dictionary values could be
expensive for high-performance scenarios, requiring a dictionary
lookup and a copy to stack of the struct. Then after changing the
struct, it would be assigned to the dictionary key again resulting in
another look up and copy operation. This improvement reduces the key
hashing to 1 (from 2) and removes all the struct copy operations.
Credit to Ben Adams.
New DateOnly and TimeOnly structs
Date- and time-only structs have been added, with the following
characteristics:
* Each represent one half of a DateTime, either only the date part,
or only the time part.
* DateOnly is ideal for birthdays, anniversary days, and business
days. It aligns with SQL Server's date type.
* TimeOnly is ideal for recurring meetings, alarm clocks, and
weekly business hours. It aligns with SQL Server's time type.
* Complements existing date/time types (DateTime, DateTimeOffset,
TimeSpan, TimeZoneInfo).
* In System namespace, shipped in CoreLib, just like existing
related types.
Perf improvements to DateTime.UtcNow
This improvement has the following benefits:
* Fixes 2.5x perf regression for getting the system time on
Windows.
* Utilizes a 5-minute sliding cache of Windows leap second data
instead of fetching with every call.
Support for both Windows and IANA time zones on all platforms
This improvement has the following benefits:
* Implicit conversion when using
TimeZoneInfo.FindSystemTimeZoneById (https://github.com/dotnet/
runtime/pull/49412)
* Explicit conversion through new APIs on TimeZoneInfo:
TryConvertIanaIdToWindowsId, TryConvertWindowsIdToIanaId, and
HasIanaId (https://github.com/dotnet/runtime/issues/49407)
* Improves cross-plat support and interop between systems that use
different time zone types.
* Removes need to use TimeZoneConverter OSS library. The
functionality is now built-in.
Improved time zone display names
Time zone display names have been improved on Unix:
* Removes ambiguity from the display names in the list returned by
TimeZoneInfo.GetSystemTimeZones.
* Leverages ICU / CLDR globalization data.
* Unix only for now. Windows still uses the registry data. This may
be changed later.
The following additional improvements were also made:
* The UTC time zone's display name and standard name were hardcoded
to English and now uses the same language as the rest of the time
zone data (CurrentUICulture on Unix, OS default language on
Windows).
* Time zone display names in Wasm use the non-localized IANA ID
instead, due to size limitations.
* TimeZoneInfo.AdjustmentRule nested class gets its
BaseUtcOffsetDelta internal property made public and gets a new
constructor that takes baseUtcOffsetDelta as a parameter. (https:
//github.com/dotnet/runtime/issues/50256)
* TimeZoneInfo.AdjustmentRule also gets miscellaneous fixes for
loading time zones on Unix (https://github.com/dotnet/runtime/
pull/49733), (https://github.com/dotnet/runtime/pull/50131)
Improved support for Windows ACLs
System.Threading.AccessControl now includes improved support for
interacting with Windows access control lists (ACLs). New overloads
were added to the OpenExisting and TryOpenExisting methods for
EventWaitHandle, Mutex and Semaphore. These overloads -- with
"security rights" instances -- enable opening existing instances of
threading synchronization objects that were created with special
Windows security attributes.
This update matches APIs available in .NET Framework and has the same
behavior.
The following examples demonstrate using these new APIs.
For Mutex:
var rights = MutexRights.FullControl;
string mutexName = "MyMutexName";
var security = new MutexSecurity();
SecurityIdentifier identity = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
MutexAccessRule accessRule = new MutexAccessRule(identity, rights, AccessControlType.Allow);
security.AddAccessRule(accessRule);
// createdMutex, openedMutex1 and openedMutex2 point to the same mutex
Mutex createdMutex = MutexAcl.Create(initiallyOwned: true, mutexName, out bool createdNew, security);
Mutex openedMutex1 = MutexAcl.OpenExisting(mutexName, rights);
MutexAcl.TryOpenExisting(mutexName, rights, out Mutex openedMutex2);
For Semaphore
var rights = SemaphoreRights.FullControl;
string semaphoreName = "MySemaphoreName";
var security = new SemaphoreSecurity();
SecurityIdentifier identity = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
SemaphoreAccessRule accessRule = new SemaphoreAccessRule(identity, rights, AccessControlType.Allow);
security.AddAccessRule(accessRule);
// createdSemaphore, openedSemaphore1 and openedSemaphore2 point to the same semaphore
Semaphore createdSemaphore = SemaphoreAcl.Create(initialCount: 1, maximumCount: 3, semaphoreName, out bool createdNew, security);
Semaphore openedSemaphore1 = SemaphoreAcl.OpenExisting(semaphoreName, rights);
SemaphoreAcl.TryOpenExisting(semaphoreName, rights, out Semaphore openedSemaphore2);
For EventWaitHandle
var rights = EventWaitHandleRights.FullControl;
string eventWaitHandleName = "MyEventWaitHandleName";
var security = new EventWaitHandleSecurity();
SecurityIdentifier identity = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
EventWaitHandleAccessRule accessRule = new EventWaitHandleAccessRule(identity, rights, AccessControlType.Allow);
security.AddAccessRule(accessRule);
// createdHandle, openedHandle1 and openedHandle2 point to the same event wait handle
EventWaitHandle createdHandle = EventWaitHandleAcl.Create(initialState: true, EventResetMode.AutoReset, eventWaitHandleName, out bool createdNew, security);
EventWaitHandle openedHandle1 = EventWaitHandleAcl.OpenExisting(eventWaitHandleName, rights);
EventWaitHandleAcl.TryOpenExisting(eventWaitHandleName, rights, out EventWaitHandle openedHandle2);
HMAC one-shot methods
The System.Security.Cryptography HMAC classes now have static methods
that allow one-shot calculation of HMACs without allocations. These
additions are similar to the one-shot methods for hash generation
that were added in a previous release.
DependentHandle is now public
The DependentHandle type is now public with the following API surface
:
namespace System.Runtime
{
public struct DependentHandle : IDisposable
{
public DependentHandle(object? target, object? dependent);
public bool IsAllocated { get; }
public object? Target { get; set; }
public object? Dependent { get; set; }
public (object? Target, object? Dependent) TargetAndDependent { get; }
public void Dispose();
}
}
It can be used by to create advanced systems, such as sophisticated
caching systems or customized versions of the ConditionalWeakTable
type. For instance, it will be used by the
WeakReferenceMessenger type in the MVVM Toolkit to avoid memory
allocations when broadcasting messages.
Portable thread pool
The .NET thread pool has been re-implemented as a managed
implementation and is now used as the default thread pool in .NET 6.
We made this change to enable all .NET applications to have access to
the same thread pool independent of whether the CoreCLR, Mono, or any
other runtime was being used. We have not observed or expect any
functional or performance impact as part of this change.
RyuJIT
The team has made many improvements to the .NET JIT compiler this
release, documented in each of the preview posts. Most of these
changes improve performance. A few of the RyuJIT highlights are
covered here.
Dynamic PGO
In .NET 6 we've enabled two forms of PGO (Profile Guided
Optimization):
* Dynamic PGO uses data gathered from the current run to optimize
the current run.
* Static PGO relies on data gathered from past runs to optimize
future runs.
Dynamic PGO was already covered in the performance section, earlier
in the post. I'll provide a re-cap.
Dynamic PGO enables the JIT to collect information at runtime about
the code paths and types that are actually used for that specific run
of the app. The JIT can then optimize the code in terms of those code
paths for sometimes very significantly improved performance. We've
seen healthy double-digit improvements in both testing and
production. There are a set of classic compiler techniques that are
not possible with either a JIT or ahead-of-time compilation without
PGO. We're now able to apply those techniques. Hot/cold splitting is
one such technique and devirtualization is another.
To enable Dynamic PGO, set DOTNET_TieredPGO=1 in the environment
where your application will run.
As stated in the performance section, dynamic PGO delivers a 26%
improvement (510K -> 640K) in requests per second for the TechEmpower
JSON "MVC" suite. That's an amazing improvement with no code changes.
Our ambition is to enable Dynamic PGO by default in a future release
of .NET, hopefully with .NET 7. We strongly encourage you to try
Dynamic PGO in your applications and give us feedback.
Full PGO
To get the full benefit of Dynamic PGO, you can set two extra
environment variables: DOTNET_TC_QuickJitForLoops=1 and
DOTNET_ReadyToRun=0. This ensures that as many methods as possible
participate in tiered compilation. We call this variant Full PGO.
Full PGO can provide larger steady-state performance benefits than
Dynamic PGO but will have slower startup times (since more methods
must be jitted at Tier 0).
You would not want to use this option for a short-running serverless
application, but it could make sense for a long-running one.
In future releases we plan to streamline and simplify these options
so that you can get the benefits of full PGO more simply and for a
wider range of applications.
Static PGO
We currently use Static PGO to optimize .NET Libraries assemblies
like System.Private.CoreLib that ship with R2R (Ready To Run).
The benefit of static PGO is that is that optimizations are made when
assemblies are compiled to R2R format with crossgen. That means that
there is runtime benefit with no runtime cost. That's very
significant and the reason why PGO is important with C++, for
example.
Loop alignment
Memory alignment is a common requirement for various operations in
modern computing. In .NET 5, we started aligning methods at the
32-byte boundary. In .NET 6, we have added a feature to perform
adaptive loop alignment that adds NOP padding instructions in a
method having loops such that the loop code starts at mod(16) or mod
(32) memory address. These changes improve and stabilize the
performance of .NET code.
In the following Bubble sort chart, data point 1 represents the point
where we started aligning methods at 32-byte boundary. Data point 2
represents the point where we started aligning inner loops that as
well. As you can see, both the performance and stability of the
benchmark improve considerably.
bubble sort chart
Hardware-accelerated structs
Structs are an important part of the CLR type system. In recent
years, they have been frequently used as a performance primitive
throughout the .NET libraries. Recent examples are ValueTask,
ValueTuple and Span. Record structs are a new example. In .NET 5
and .NET 6, we've been improving performance for structs, in part by
ensuring that structs can be held in ultra-fast CPU registers when
they are locals, arguments or return values of methods). This is
particularly beneficial for APIs that compute with vectors.
Stabilize performance measurements
There is a tremendous amount of engineering systems work on the team
that never appears on the blog. That will be true for any hardware or
software product you use. The JIT team took on a project to stabilize
performance measurements with the goal of increasing the value of
regressions that are auto-reported by our internal performance lab
automation. This project is interesting because of the in-depth
investigation and the product changes that were required to enable
stability. It also demonstrates the scale at which we measure to
maintain and improve performance.
perf-#43227, Announcing .NET 6 -- The Fastest .NET Yet
This image demonstrates unstable performance measurements where
performance fluctuated between a slow and fast in successive runs.
The x-axis is the date of the test and y-axis is the time of the test
in nanoseconds. By the end of the chart (after these changes were
committed), you can see that measurements stabilized, with the best
result. This image demonstrates a single test. There are many more
tests that are demonstrated to have similar behavior at dotnet/
runtime #43227.
Ready-to-run Code / Crossgen 2
Crossgen2 is a replacement of the crossgen tool. It is intended to
satisfy two outcomes:
* Make crossgen development more efficient.
* Enable a set of capabilities that are not currently possible with
crossgen.
This transition is somewhat similar to the native code csc.exe to
managed-code Roslyn compiler. Crossgen2 is written in C#, however, it
doesn't expose a fancy API like Roslyn does.
There are perhaps a half-dozen projects we had/have planned for .NET
6 and 7 that are dependent on crossgen2. The proposal for vector
instruction default is a great example of a crossgen2 capability and
product change that we wanted to make for .NET 6 but is more likely
.NET 7. Version bubbles are another good example.
Crossgen2 enables cross-compilation (hence the name "crossgen")
across operating system and architecture dimensions. That means that
you will be able to use a single build machine to generate native
code for all targets, at least as it relates to ready-to-run code.
Running and testing that code is a different story, however, and
you'll need appropriate hardware and operating systems for that.
The first step is to compile the platform itself with crossgen2. We
completed that with .NET 6, for all architectures. As a result, we
were able to retire the old crossgen this release. Note that
crossgen2 only applies to CoreCLR and not to Mono-based applications
(which have a separate set of code generation tools).
This project -- at least at first -- is not oriented on performance.
The goal is to enable a much better architecture for hosting the
RyuJIT (or any other) compiler to generate code in an offline manner
(not requiring or starting the runtime).
You might say "hey ... don't you have to start the runtime to run
crossgen2 if it is written in C#?" Yes, but that's not what is meant
by "offline" in this context. When crossgen2 runs, we're not using
the JIT that comes with the runtime that crossgen2 is running on to
generate ready-to-run (R2R) code. That won't work, at least not with
the goals we have. Imagine crossgen2 is running on an x64 machine,
and we need to generate code for Arm64. Crossgen2 loads the Arm64
RyuJIT -- compiled for x64 -- as a native plugin, and then uses it to
generate Arm64 R2R code. The machine instructions are just a stream
of bytes that are saved to a file. It can also work in the opposite
direction. On Arm64, crossgen2 can generate x64 code using the x64
RyuJIT compiled to Arm64. We use the same approach to target x64 code
on x64 machines. Crossgen2 loads a RyuJIT built for whatever
configuration is needed. That may seem complicated, but it's the sort
of system you need in place if you want to enable a seamless
cross-targeting model, and that's exactly what we want.
We hope to use the term "crossgen2" for just one release, after which
it will replace the existing crossgen, and then we'll go back to
using the "crossgen" term for "crossgen2".
.NET Diagnostics: EventPipe
EventPipe is our cross-platform mechanism for egressing events,
performance data, and counters, either in-process or out-of-process.
Starting with .NET 6, we've moved the implementation from C++ to C.
With this change, Mono uses EventPipe as well. This means that both
CoreCLR and Mono use the same eventing infrastructure, including the
.NET Diagnostics CLI Tools.
This change also came with small reduction in size for CoreCLR:
lib after size - before size diff
libcoreclr.so 7037856 - 7049408 -11552
We've also made some changes that improve EventPipe throughput while
under load. Over the first few previews, we've made a series of
changes that result in throughput improvements as high as 2.06x what
.NET 5 was capable of:
Announcing .NET 6 -- The Fastest .NET Yet
Higher is better for this benchmark. .NET 6 is the orange line and
.NET 5 is the blue one.
SDK
The following improvements were made to the .NET SDK.
CLI install of .NET 6 SDK Optional Workloads
.NET 6 introduces the concept of SDK workloads. Workload are optional
components can be installed on top of the .NET SDK to enable various
scenarios. The new workloads in .NET 6 are: .NET MAUI and Blazor
WebAssembly AOT workloads. We will likely create new workloads
(possibly from the existing SDK) in .NET 7. The biggest benefit of
workloads is size reduction and optionality. We want to make the SDK
smaller over time and enable installing just the components you need.
This model will be good for developer machines and even better for
CI.
Visual Studio users don't really need to worry about workloads. The
workloads feature has been designed so that an installation
orchestrator like Visual Studio can install workloads for you.
Workloads can be managed directly via the CLI.
The workloads feature exposes multiple verbs for managing workloads,
including the following ones:
* dotnet workload restore -- installs the workloads required by a
given project.
* dotnet workload install -- installs a named workload.
* dotnet workload list -- lists the workloads you have installed.
* dotnet workload update -- updates all installed workloads to the
newest available version.
The update verb queries nuget.org for updated workload manifests,
updates local manifests, downloads new versions of the installed
workloads, and then removes all old versions of a workload. This is
analogous to apt update && apt upgrade -y (used on Debian-based Linux
distros). It is reasonable to think of workloads as a private package
manager for the SDK. It is private in the sense that it is only
available for SDK components. We may reconsider that in future.
The dotnet workload commands operate in the context of the given SDK.
Imagine you have both .NET 6 and .NET 7 installed. The workloads
commands will provide different results for each SDK since the
workloads will be different (at least different versions of the same
workloads).
Note that dotnet workload install copies the workloads from NuGet.org
into your SDK install so will need to be run elevated or use sudo if
the SDK install location is protected (meaning at an admin/root
location).
Built-in SDK version checking
To make it easier to track when new versions of the SDK and Runtimes
are available, we've added a new command to the .NET 6 SDK.
dotnet sdk check
It tells you if there is a newer version available for any of the
.NET SDKs, runtimes, or workloads you have installed. You can see the
new experience in the following image.
sdkcheck
dotnet new
You can now search NuGet.org for new templates with dotnet new
--search.
Other improvements to template installation include support for the
--interactive switch to support authorization credentials for private
NuGet feeds.
Once CLI templates are installed, you can check if updates are
available via --update-check and --update-apply.
NuGet Package Validation
Package Validation tools enables NuGet library developers to validate
that their packages are consistent and well-formed.
This includes:
* Validate that there are no breaking changes across versions.
* Validate that the package has the same set of publics APIs for
all runtime-specific implementations.
* Determine any target-framework- or runtime- applicability gaps.
This tool is available via the Microsoft.DotNet.PackageValidation
package.
More Roslyn Analyzers
In .NET 5, we shipped approximately 250 analyzers with the .NET SDK.
Many of them already existed but were shipped out-of-band as NuGet
packages. We've adding more analyzers for .NET 6.
By default, most of the new analyzers are enabled at Info level. You
can enable these analyzers at Warning level by configuring the
analysis mode like this: All.
We published the set of analyzers we wanted for .NET 6 (plus some
extras) and then made most of them up-for-grabs. The community has
added several implementations, including these ones.
Contributor Issue Title
Newell dotnet/runtime Use span-based string.Concat
Clark #33777
Newell dotnet/runtime Prefer string.AsSpan() over
Clark #33784 string.Substring() when parsing
Newell dotnet/runtime Override Stream.ReadAsync/WriteAsync
Clark #33789
Newell dotnet/runtime Replace Dictionary<,>.Keys.Contains with
Clark #35343 ContainsKey
Newell dotnet/runtime Use String.Equals instead of
Clark #45552 String.Compare
Meik Tranel dotnet/runtime Use String.Contains(char) instead of
#47180 String.Contains(String)
Thanks Meik Tranel and Newell Clark.
Enable custom guards for Platform Compatibility Analyzer
The CA1416 Platform Compatibility analyzer already recognizes
platform guards using the methods inOperatingSystem and
RuntimeInformation, such as OperatingSystem.IsWindows and
OperatingSystem.IsWindowsVersionAtLeast. However, the analyzer does
not recognize any other guard possibilities like the platform check
result cached in a field or property, or complex platform check logic
is defined in a helper method.
For allowing custom guard possibilities we added new attributes
SupportedOSPlatformGuard and UnsupportedOSPlatformGuard for
annotating the custom guard members with the corresponding platform
name and/or version. This annotation is recognized and respected by
the Platform Compatibility analyzer's flow analysis logic.
Usage
[UnsupportedOSPlatformGuard("browser")] // The platform guard attribute
#if TARGET_BROWSER
internal bool IsSupported => false;
#else
internal bool IsSupported => true;
#endif
[UnsupportedOSPlatform("browser")]
void ApiNotSupportedOnBrowser() { }
void M1()
{
ApiNotSupportedOnBrowser(); // Warns: This call site is reachable on all platforms.'ApiNotSupportedOnBrowser()' is unsupported on: 'browser'
if (IsSupported)
{
ApiNotSupportedOnBrowser(); // Not warn
}
}
[SupportedOSPlatform("Windows")]
[SupportedOSPlatform("Linux")]
void ApiOnlyWorkOnWindowsLinux() { }
[SupportedOSPlatformGuard("Linux")]
[SupportedOSPlatformGuard("Windows")]
private readonly bool _isWindowOrLinux = OperatingSystem.IsLinux() || OperatingSystem.IsWindows();
void M2()
{
ApiOnlyWorkOnWindowsLinux(); // This call site is reachable on all platforms.'ApiOnlyWorkOnWindowsLinux()' is only supported on: 'Linux', 'Windows'.
if (_isWindowOrLinux)
{
ApiOnlyWorkOnWindowsLinux(); // Not warn
}
}
}
Closing
Welcome to .NET 6. It is another huge .NET release, with near-equal
servings of performance, functionality, usability, and security
improvements. We hope you find many improvements that end up making
you more efficient and capable in your every-day development and
incease performance or reduce the costs of your apps in production.
We've already started hearing good things from those among you have
already started to use .NET 6.
At Microsoft, we're also in the early phases of .NET 6 deployment,
with a few key apps already in production and many more soon to come
in the coming weeks and months.
.NET 6 is our latest LTS release. We encourage everyone to move to
it, particularly if you are using .NET 5. We're expecting it to be
the fastest adopted .NET release ever.
This release is the result of at least 1000 people (but probably a
lot more). That includes .NET team from Microsoft and many more in
the community. I've tried to include many community-contributed
features in this post. Thank you for taking the time to create those
and work through our process. I hope that the experience was a good
one and that even more people will contribute.
This post is the result of a collaboration of many talented people.
The contributions includes the feature content that the team provides
throughout the release, significant new content created for this
final post, and a ton of technical and prose corrections that are
required to bring the final content up to the quality you deserve.
It's been a pleasure crafting it and all the other posts for you.
Thanks for being a .NET developer.
[png]
Richard Lander
Program Manager, .NET Team
Follow
Posted in .NET
Read next
.NET November 2021 Updates - 5.0.12 and 3.1.21
Today, we are releasing the .NET November 2021 Updates. These updates
contains reliability and other improvements. See the individual
release notes for details on ...
[png] Sanket Kalaskar November 8, 2021
0 comment
Announcing .NET MAUI Preview 10
.NET MAUI Preview 10 includes a single installation experience with
Windows App SDK, and new controls such as CollectionView and
IndicatorView.
[png] David Ortinau November 8, 2021
0 comment
13 comments
Leave a commentCancel reply
Log in to join the discussion.
* [png]
Calvin Allen November 8, 2021 9:46 am
collapse this comment
First!
Kudos to all the teams and folx involved in making this release a
reality!
And, kudos to Rich for what might be the longest blog post I've
ever read!
Log in to Reply
+ [png]
Richard LanderMicrosoft employee November 8, 2021 11:26 am
collapse this comment
Thanks. Folks are always delighted when they see the features
they work on showing up in the officials post, so I try to
cover as many as I can.
Log in to Reply
* [png]
devtommy November 8, 2021 10:11 am
collapse this comment
First, congratulations for the teams!
When will .net 6/7/8 comming to UWP?
Log in to Reply
+ [png]
Charles Roddie November 8, 2021 10:53 am
collapse this comment
It's coming with WinUI but without AOT which is a requirement
for us to deploy apps with it (and thefore a blocker for
using MAUI). Windows now is the only platform that we can't
get AOT compilation on with .Net 6. So it's very important to
get WinUI/NativeAOT on the tracker: https://github.com/dotnet
/runtime/issues/61231#issuecomment-961586741
Log in to Reply
+ [png]
Richard LanderMicrosoft employee November 8, 2021 11:22 am
collapse this comment
This post discusses the future of WinUI and UWP. https://
github.com/microsoft/WindowsAppSDK/discussions/1615
Log in to Reply
* [png]
Jun Yan Xia November 8, 2021 10:30 am
collapse this comment
congratulations for the teams!
Log in to Reply
* [png]
hitesh davey November 8, 2021 12:19 pm
collapse this comment
Congratulations MS VS and .NET teams! You guys have done a great
job indeed.
Log in to Reply
* [png]
Alexander Dovgaluk November 8, 2021 1:07 pm
collapse this comment
Why I don't see anything about creating dynamically-linked
libraries in .NET?
Is it still not supported?
Log in to Reply
+ [png]
Richard LanderMicrosoft employee November 8, 2021 1:09 pm
collapse this comment
We recommend DNNE: https://github.com/AaronRobinsonMSFT/DNNE.
Is that what you want?
Log in to Reply
o [png]
Alexander Dovgaluk November 8, 2021 1:11 pm
collapse this comment
Nope. I want native solution, not some third-party
library.
Log in to Reply
* [png]
Jamie Nordmeyer November 8, 2021 1:49 pm
collapse this comment
Congrats team!!! I've been looking forward to this release! I
have a question, though. Do you know when OmniSharp will fully
support .NET 6? I tried using the File Scoped Namespaces feature
after installing .NET 6. The code builds, but VSCode/OmniSharp
syntax highlighting goes funky. I did some searching before
posting this and couldn't find an answer, so I figured that it
couldn't hurt to ask here. Thanks again!!!
Log in to Reply
+ [png]
Tim HeuerMicrosoft employee November 8, 2021 2:24 pm
collapse this comment
@Jamie - thanks for the ping here. Would you mind logging an
issue of what you are seeing in the VSCode/OmniSharp
experience? Will help us point the team in that direction a
bit more.
Log in to Reply
* [png]
Emmanuel Adebiyi November 8, 2021 1:50 pm
collapse this comment
Awesome job. Really impressive stuff.
Log in to Reply
Relevant Links
.NET Download
.NET Hello World
.NET Meetup Events
.NET Documentation
.NET API Browser
.NET SDKs
.NET Application Architecture Guides
Web apps with ASP.NET Core
Mobile apps with Xamarin.Forms
Microservices with Docker Containers
Modernizing existing .NET apps to the cloud
Archive
November 2021
October 2021
September 2021
August 2021
July 2021
June 2021
May 2021
April 2021
March 2021
February 2021
January 2021
December 2020
November 2020
October 2020
September 2020
August 2020
July 2020
June 2020
May 2020
April 2020
March 2020
February 2020
January 2020
December 2019
November 2019
October 2019
September 2019
August 2019
July 2019
June 2019
May 2019
April 2019
March 2019
February 2019
January 2019
December 2018
November 2018
October 2018
September 2018
August 2018
July 2018
June 2018
May 2018
April 2018
March 2018
February 2018
January 2018
December 2017
November 2017
October 2017
September 2017
August 2017
July 2017
June 2017
May 2017
April 2017
March 2017
February 2017
January 2017
December 2016
November 2016
October 2016
September 2016
August 2016
July 2016
June 2016
May 2016
April 2016
March 2016
February 2016
January 2016
December 2015
November 2015
October 2015
September 2015
August 2015
July 2015
June 2015
May 2015
April 2015
March 2015
February 2015
January 2015
December 2014
November 2014
October 2014
September 2014
August 2014
July 2014
June 2014
May 2014
April 2014
March 2014
February 2014
January 2014
December 2013
November 2013
October 2013
September 2013
August 2013
July 2013
June 2013
May 2013
April 2013
March 2013
February 2013
January 2013
December 2012
November 2012
October 2012
September 2012
August 2012
July 2012
June 2012
May 2012
April 2012
March 2012
February 2012
January 2012
October 2011
September 2011
August 2011
June 2011
April 2011
March 2011
February 2011
January 2011
December 2010
November 2010
October 2010
September 2010
August 2010
July 2010
June 2010
May 2010
April 2010
March 2010
February 2010
December 2009
November 2009
October 2009
September 2009
August 2009
July 2009
June 2009
May 2009
April 2009
March 2009
February 2009
January 2009
December 2008
November 2008
October 2008
September 2008
August 2008
July 2008
June 2008
May 2008
April 2008
March 2008
February 2008
January 2008
December 2007
November 2007
October 2007
September 2007
August 2007
July 2007
June 2007
May 2007
April 2007
March 2007
February 2007
January 2007
December 2006
November 2006
October 2006
September 2006
August 2006
July 2006
June 2006
May 2006
April 2006
March 2006
February 2006
January 2006
October 2005
July 2005
May 2005
December 2004
November 2004
September 2004
June 2004
Topics
.NET
ASP.NET
.NET Core
ASP.NET Core
.NET Framework
Blazor
Entity Framework
C#
Azure
Visual Studio
WPF
ML.NET
Performance
Machine Learning
F#
AI Machine Learning
SignalR
.NET MAUI
Containers
Security
Community Standup
.NET Internals
WinForms
Conversations
Game Development
Cloud
Show dotnet
Lifecycle
Debugging
Docker
WCF
WebHooks
Maoni
Async
Concurrency
GC
TPL
Visual Basic
Featured
Apache
Big Data
Spark for .NET
Static Analysis
IoT
Orchard Core
SQL Server
LOH
XAML
Stay informed
Login
Insert/edit link
Close
Enter the destination URL
URL [ ]
Link Text [ ]
[ ] Open link in a new tab
Or link to existing content
Search [ ]
No search term specified. Showing recent items. Search or use up and
down arrow keys to select an item.
Cancel
[Add Link]
Code Block
x
Paste your code snippet [ ]
Cancel Ok
What's new
* Surface Pro 8
* Surface Laptop Studio
* Surface Pro X
* Surface Go 3
* Surface Duo 2
* Surface Pro 7+
* Windows 11 apps
* HoloLens 2
Microsoft Store
* Account profile
* Download Center
* Microsoft Store support
* Returns
* Order tracking
* Virtual workshops and training
* Microsoft Store Promise
* Flexible Payments
Education
* Microsoft in education
* Office for students
* Office 365 for schools
* Deals for students & parents
* Microsoft Azure in education
Enterprise
* Azure
* AppSource
* Automotive
* Government
* Healthcare
* Manufacturing
* Financial services
* Retail
Developer
* Microsoft Visual Studio
* Windows Dev Center
* Developer Center
* Microsoft developer program
* Channel 9
* Microsoft 365 Dev Center
* Microsoft 365 Developer Program
* Microsoft Garage
Company
* Careers
* About Microsoft
* Company news
* Privacy at Microsoft
* Investors
* Diversity and inclusion
* Accessibility
* Security
English (United States)
* Sitemap
* Contact Microsoft
* Privacy
* Manage cookies
* Terms of use
* Trademarks
* Safety & eco
* About our ads
* (c) Microsoft 2021