Today we released .NET 7 Preview 5. This preview of .NET 7 includes improvements to Generic Math which make the lives of API authors easier, a new Text Classification API for ML.NET that adds state-of-the-art deep learning techniques for natural language processing, various improvements to source code generators and a new Roslyn analyzer and fixer for RegexGenerator and multiple performance improvements in the areas of CodeGen, Observability, JSON serialization / deserialization and working with streams.
You can download .NET 7 Preview 5, for Windows, macOS, and Linux.
- Installers and binaries
- Container images
- Linux packages
- Release notes
- Known issues
- GitHub issue tracker
.NET 7 Preview 5 has been tested with Visual Studio 17.3 Preview 2. We recommend you use the preview channel builds if you want to try .NET 7 with Visual Studio family products. If you’re on macOS, we recommend using the latest Visual Studio 2022 for Mac preview. Now, let’s get into some of the latest updates in this release.
Observability
The goal of observability is to help you better understand the state of your application as scale and technical complexity increases.
Expose performant ActivityEvent and ActivityLink tags enumerator methods
The exposed methods can be used in performance critical scenarios to enumerate the Tag objects without any extra allocations and with fast items access.
var tags = new List<KeyValuePair<string, object?>>()
{
new KeyValuePair<string, object?>("tag1", "value1"),
new KeyValuePair<string, object?>("tag2", "value2"),
};
ActivityLink link = new ActivityLink(default, new ActivityTagsCollection(tags));
foreach (ref readonly KeyValuePair<string, object?> tag in link.EnumerateTagObjects())
{
// Consume the link tags without any extra allocations or value copying.
}
ActivityEvent e = new ActivityEvent("SomeEvent", tags: new ActivityTagsCollection(tags));
foreach (ref readonly KeyValuePair<string, object?> tag in e.EnumerateTagObjects())
{
// Consume the event's tags without any extra allocations or value copying.
}
System.Text.Json
Polymorphism
System.Text.Json now supports serializing and deserializing polymorphic type hierarchies using attribute annotations:
[JsonDerivedType(typeof(Derived))]
public class Base
{
public int X { get; set; }
}
public class Derived : Base
{
public int Y { get; set; }
}
This configuration enables polymorphic serialization for Base
, specifically when the runtime type is Derived
:
Base value = new Derived();
JsonSerializer.Serialize<Base>(value); // { "X" : 0, "Y" : 0 }
Note that this does not enable polymorphic deserialization since the payload would be roundtripped as Base
:
Base value = JsonSerializer.Deserialize<Base>(@"{ ""X"" : 0, ""Y"" : 0 }");
value is Derived; // false
Using Type Discriminators
To enable polymorphic deserialization, users need to specify a type discriminator for the derived class:
[JsonDerivedType(typeof(Base), typeDiscriminator: "base")]
[JsonDerivedType(typeof(Derived), typeDiscriminator: "derived")]
public class Base
{
public int X { get; set; }
}
public class Derived : Base
{
public int Y { get; set; }
}
Which will now emit JSON along with type discriminator metadata:
Base value = new Derived();
JsonSerializer.Serialize<Base>(value); // { "$type" : "derived", "X" : 0, "Y" : 0 }
which can be used to deserialize the value polymorphically:
Base value = JsonSerializer.Deserialize<Base>(@"{ ""$type"" : ""derived"", ""X"" : 0, ""Y"" : 0 }");
value is Derived; // true
Type discriminator identifiers can also be integers, so the following form is valid:
[JsonDerivedType(typeof(Derived1), 0)]
[JsonDerivedType(typeof(Derived2), 1)]
[JsonDerivedType(typeof(Derived3), 2)]
public class Base { }
JsonSerializer.Serialize<Base>(new Derived2()); // { "$type" : 1, ... }
Utf8JsonReader.CopyString
Until today, Utf8JsonReader.GetString()
has been the only way users could consume decoded JSON strings. This will always allocate a new string, which might be unsuitable for certain performance-sensitive applications. The newly included CopyString
methods allow copying the unescaped UTF-8 or UTF-16 strings to a buffer owned by the user:
int valueLength = reader.HasReadOnlySequence ? checked((int)ValueSequence.Length) : ValueSpan.Length;
char[] buffer = ArrayPool<char>.Shared.Rent(valueLength);
int charsRead = reader.CopyString(buffer);
ReadOnlySpan<char> source = buffer.Slice(0, charsRead);
ParseUnescapedString(source); // handle the unescaped JSON string
ArrayPool<char>.Shared.Return(buffer);
Or if handling UTF-8 is preferable:
ReadOnlySpan<byte> source = stackalloc byte[0];
if (!reader.HasReadOnlySequence && !reader.ValueIsEscaped)
{
source = reader.ValueSpan; // No need to copy to an intermediate buffer if value is span without escape sequences
}
else
{
int valueLength = reader.HasReadOnlySequence ? checked((int)ValueSequence.Length) : ValueSpan.Length;
Span<byte> buffer = valueLength <= 256 ? stackalloc byte[256] : new byte[valueLength];
int bytesRead = reader.CopyString(buffer);
source = buffer.Slice(0, bytesRead);
}
ParseUnescapedBytes(source);
Source Generation improvements
Added source generation support for IAsyncEnumerable<T>
(#59268), JsonDocument
(#59954) and DateOnly
/TimeOnly
(#53539) types.
For example:
[JsonSerializable(typeof(typeof(MyPoco))]
public class MyContext : JsonSerializerContext {}
public class MyPoco
{
// Use of IAsyncEnumerable that previously resulted
// in JsonSerializer.Serialize() throwing NotSupportedException
public IAsyncEnumerable<int> Data { get; set; }
}
// It now works and no longer throws NotSupportedException
JsonSerializer.Serialize(new MyPoco { Data = ... }, MyContext.MyPoco);
System.IO.Stream
ReadExactly and ReadAtLeast
One of the most common mistakes when using Stream.Read()
is that Read()
may return less data than what is available in the Stream
and less data than the buffer being passed in. And even for programmers who are aware of this, having to write the same loop every single time they want to read from a Stream
is annoying.
To help this situation, we have added new methods to the base System.IO.Stream
class:
namespace System.IO;
public partial class Stream
{
public void ReadExactly(Span<byte> buffer);
public void ReadExactly(byte[] buffer, int offset, int count);
public ValueTask ReadExactlyAsync(Memory<byte> buffer, CancellationToken cancellationToken = default);
public ValueTask ReadExactlyAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default);
public int ReadAtLeast(Span<byte> buffer, int minimumBytes, bool throwOnEndOfStream = true);
public ValueTask<int> ReadAtLeastAsync(Memory<byte> buffer, int minimumBytes, bool throwOnEndOfStream = true, CancellationToken cancellationToken = default);
}
The new ReadExactly
methods are guaranteed to read exactly the number of bytes requested. If the Stream ends before the requested bytes have been read, an EndOfStreamException
is thrown.
using FileStream f = File.Open("readme.md");
byte[] buffer = new byte[100];
f.ReadExactly(buffer); // guaranteed to read 100 bytes from the file
The new ReadAtLeast
methods will read at least the number of bytes requested. It can read more if more data is readily available, up to the size of the buffer. If the Stream ends before the requested bytes have been read an EndOfStreamException
is thrown (in advanced cases when you want the benefits of ReadAtLest
but you also want to handle the end-of-stream scenario yourself, you can opt out of throwing the exception).
using FileStream f = File.Open("readme.md");
byte[] buffer = new byte[100];
int bytesRead = f.ReadAtLeast(buffer, 10);
// 10 <= bytesRead <= 100
New Roslyn analyzer and fixer for RegexGenerator
In Regular Expression Improvements in .NET 7 Stephen Toub describes the new RegexGenerator source generator, which allows you to statically generate regular expressions at compile time resulting in better performance. To take advantage of this, first you have to find places in your code where it could be used and then make each code change. This sounds like the perfect job for a Roslyn analyzer and fixer, so we added one in Preview 5.
Analyzer
The new analyzer is included in .NET 7, and will search for uses of Regex
that could be converted to use the RegexGenerator source generator instead. The analyzer will detect uses of the Regex
constructors, as well as uses of the Regex
static methods that meet the following criteria:
- Parameters supplied have a known value at compile time. The source generator’s output depends on these values, so they must be known at compile time.
- They are part of an app that targets .NET 7. The new analyzer ships inside the .NET 7 targeting pack and only apps targeting .NET 7 are eligible for this analyzer.
- The
LangVersion
(learn more) is higher than10
. For the time being the regex source generator requiresLangVersion
to be set topreview
.
Here is the new analyzer in action in Visual Studio:
Code fixer
The code fixer is also included in .NET 7 and it does two things. First, it suggests a RegexGenerator source generator method and gives you the option to override the default name. Then it replaces the original code with a call to the new method.
Here is the new code fixer in action in Visual Studio:
Generic Math
In .NET 6 we previewed a feature called Generic Math which allows .NET developers to take advantage of static APIs, including operators, from within generic code. This feature will directly benefit API authors who can simplify their codebase. Other developers will benefit indirectly as the APIs they consume will start supporting more types without the requirement for each and every numeric type to get explicit support.
In .NET 7 we have made improvements to the implementation and responded to feedback from the community. For more information on the changes and available APIs please see our Generic Math specific announcement.
System.Reflection performance improvements when invoking members
The overhead of using reflection to invoke a member (whether a method, constructor or a property gettersetter) has been substantially reduced when the invoke is done several times on the same member. Typical gains are 3-4x faster.
Using the BenchmarkDotNet
package:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Reflection;
namespace ReflectionBenchmarks
{
internal class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<InvokeTest>();
}
}
public class InvokeTest
{
private MethodInfo? _method;
private object[] _args = new object[1] { 42 };
[GlobalSetup]
public void Setup()
{
_method = typeof(InvokeTest).GetMethod(nameof(InvokeMe), BindingFlags.Public | BindingFlags.Static)!;
}
[Benchmark]
// *** This went from ~116ns to ~39ns or 3x (66%) faster.***
public void InvokeSimpleMethod() => _method!.Invoke(obj: null, new object[] { 42 });
[Benchmark]
// *** This went from ~106ns to ~26ns or 4x (75%) faster. ***
public void InvokeSimpleMethodWithCachedArgs() => _method!.Invoke(obj: null, _args);
public static int InvokeMe(int i) => i;
}
}
ML.NET Text Classification API
Text classification is the process of applying labels or categories to text.
Common use cases include:
- Categorizing e-mail as spam or not spam
- Analyzing sentiment as positive or negative from customer reviews
- Applying labels to support tickets
Text classification is a subset of classification, so today you could solve text classification problems with the existing classification algorithms in ML.NET. However, those algorithms don’t address common challenges with text classification as well as modern deep learning techniques.
We are excited to introduce the ML.NET Text Classification API, an API that makes it easier for you to train custom text classification models and brings the latest state-of-the-art deep learning techniques for natural language processing to ML.NET.
For more details please see our ML.NET specific announcement
CodeGen
Many thanks to the community contributors.
@singleaccretion made 23 PR contributions during Preview 5 with the highlights being:
- Improve the redundant branch optimization to handle more side effects #68447
- PUTARG_STK/x86: mark push [mem] candidates reg optional #68641
- Copy propagate on LCL_FLDs #68592
@Sandreenko completed allowing StoreLclVar src to be IND/FLD #59315. @hez2010 fixed CircleInConvex test in #68475.
More contributions from @anthonycanino, @aromaa and @ta264 are included in the sections to follow.
Arm64
#68363 consolidated ‘msub’ (multiplies two register values, subtracts the product from a third register value) and ‘madd’ (multiplies two register values, adds a third register value) logic.
Arm64: Have CpBlkUnroll and InitBlkUnroll use SIMD registers for initialization of copying a block of memory smaller than 128 bytes (see perf improvement details).
Loop Optimization
#67930 Handle more scenarios for loop cloning now supports loops that go backwards or forwards with the increment of > 1 (see perf improvement details).
#68588 Hoist the nullchecks for ‘this’ object moves the nullchecks on an object outside the loop (see perf improvement details).
x86/x64 Optimizations
- #67182 Emit shlx, sarx, shrx on x64 optimized mov+shl, sar or shr to shlx, sarx or shrx on x64.
- #68091 enabled UMOD optimization for x64.
- @anthonycanino added X86Serialize hardware intrinsic in #68677.
- @aromaa optimized bswap+mov to movbe in #66965.
- @ta264 fixed linux-x86 compilation for clr.alljits subset in #68046.
General Optimizations
- PR#68105 enabled multiple nested “no GC” region requests.
- PR#69034 removed “promoted parameter” tailcall limitation.
Modernize JIT
As the community has ramped up its contributions to the JIT code base, it has become important to restructure and modernize our codebase to enable our contributors to easily ramp up and rapidly develop code.
In Preview 5 we did a lot of work on the internals, cleaned up the JIT’s intermediate representation and removed limitations imposed by past design decisions. In many cases this work resulted in less memory usage and higher throughput of the JIT itself, while in other cases it resulted in better code quality. Here are some highlights:
The above allowed us to remove an old limitation in the JIT’s inliner when inlining functions with parameters of byte/sbyte/short/ushort type, resulting in better code quality (allow the inliner to substitute for small arguments #69068)
One area that needed improvement was better understanding of unsafe code involving reads and writes of structs and struct fields. @SingleAccretion contributed great changes in this area by switching the JIT’s internal model to a more general “physical” model. This paves the way for the JIT to better reason about unsafe code using features like struct reinterpretation:
Other minor cleanups were also made to simplify the JIT IR:
- Remove GTF_LATE_ARG #68617
- Substitute GT_RET_EXPR in inline candidate arguments #69117
- Remove stores as operands of calls in LIR #68460
Enable library trimming
As we’ve previously described, trimming lets the SDK remove unused code from your self-contained apps to make them smaller. However, trim warnings can indicate that an app is not compatible with trimming. To make apps compatible, all their references also have to be compatible.
For that, we need libraries to adopt trimming as well. In Preview 5, we’ve worked to make finding and fixing trim warnings in libraries even easier using Roslyn analyzers. To turn see trim warnings for your library, add <IsTrimmable>true</IsTrimmable>
to your project file. After fixing the warnings, trimmed apps using your library will be smaller and compatible with trimming. See Prepare .NET libraries for trimming – .NET | Microsoft Docs for more info about library trimming.
Contributor spotlight: Steve Dunn
Steve documented the beginning of his contributions to .NET and we are grateful for all of them. We want to take the opportunity to specifically call out Steve’s work on Microsoft.Extensions.Configuration. He’s taken on high-impact issues that require significant technical knowledge and domain expertise and a good example of this is support immutable types with configuration binding.
Not only is Steve up to the technical challenge, but he is also understanding, patient and thoughtful. In his most recent PR Steve received 123 comments from six reviews, and involved 41 commits. Steve welcomed the feedback and took initiative to achieve a resolution that everybody was happy with. Steve is a perfect example of a high-quality community contributor.
In Steve’s own words:
I currently write services in .NET for corporate clients. In the past I’ve published packaged products, including Arabian Themes, which was an Arabic conversion of “Windows 95 Plus!”
I started writing software in the mid 80’s, beginning with BBC Basic and then Commodore 64 games, including Better dead than alien, Call me psycho, Space relief, Galaxia 7, Thunder Hawk and Zone Z
I regularly write articles on https://dunnhq.com and I’m currently working on curing Primitive Obsession with Vogen.
Contributor spotlight: Anthony Canino
Anthony recently added X86Serialize hardware intrinsic based on an API proposal that exposes xarch serialize instruction referencing a couple of chapters of Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A and Intel Architecture Instruction Set Extensions and Future Features.
This allows a developer to issue X86Serialize.Serialize()
before a conditional such as:
X86Serialize.Serialize();
if (someCondition)
{
// ...
}
Thank you Anthony for your multiple contributions to .NET!
In Anthony’s own words:
My name is Anthony Canino and I am a Software Engineer at Intel, based in Seattle Washington, with a passion for compiler development and language design. As a newly located PNW resident, I enjoy taking advantage of all the wonderful hiking and beautiful nature the area has to offer with my two Siberian huskies.
Our team at Intel believes in developer-experience first, and we support the .NET ecosystem and wish to continue to see the platform thrive and serve as the developer-focused centerpiece that it is today. Some of my first contributions to .NET were some peephole optimizations in the xarch code emitter that really helped me to understand some of the lower-level details of code generation. Specifically to .NET’s RyuJIT, I’ve found the quality of the code and the documentation significantly reduce the barrier of entry toward making contributions in a challenging space.
What I love about contributing to .NET is the dedication that Microsoft has put forth to enable open source developers to participate in the project with production-level resources — documentation and testing infrastructure and pipeline — and professional developer community support and planning. This kind of treatment really allows external contributors to feel part of the overall direction of the project and encourages further contributions to the ecosystem.
Targeting .NET 7
To target .NET 7, you need to use a .NET 7 Target Framework Moniker (TFM) in your project file. For example:
<TargetFramework>net7.0</TargetFramework>
The full set of .NET 7 TFMs, including operating-specific ones follows.
net7.0
net7.0-android
net7.0-ios
net7.0-maccatalyst
net7.0-macos
net7.0-tvos
net7.0-windows
We expect that upgrading from .NET 6 to .NET 7 should be straightforward. Please report any breaking changes that you discover in the process of testing existing apps with .NET 7.
Support
.NET 7 is a Short Term Support (STS) release, meaning it will receive free support and patches for 18 months from the release date. It’s important to note that the quality of all releases is the same. The only difference is the length of support. For more about .NET support policies, see the .NET and .NET Core official support policy.
We recently recently changed the “Current” name to “Short Term Support (STS)”. We’re in the process of rolling out that change.
Breaking changes
You can find the most recent list of breaking changes in .NET 7 by reading the Breaking changes in .NET 7 document. It lists breaking changes by area and release with links to detailed explanations.
To see what breaking changes are proposed but still under review, follow the Proposed .NET Breaking Changes GitHub issue.
Roadmaps
Releases of .NET include products, libraries, runtime, and tooling, and represent a collaboration across multiple teams inside and outside Microsoft. You can learn more about these areas by reading the product roadmaps:
Closing
We appreciate and thank you for your all your support and contributions to .NET. Please give .NET 7 Preview 5 a try and tell us what you think!
Hi Angelos,
Thanks for the nice summary!
Have one question: In the code example above for the new method, for the example you use an array from the ArrayPool, but for the example on copying the s directly, you do declare a with either a stackalloc (-when less 256) or a regular heap allocation (-when greater 256).
Just wanted to learn what's the logic behind the difference. are there any performance benefits?
The ArrayPool logic was omitted in the second example for brevity, but nothing prevents you from using it in your code otherwise.
Thanks Eirik George Tsarpalis for your reply!
As far as I understand the main benefit of using the new
CopyString()
api instead of GetString(), is about avoiding memory allocations, so here is my confusion, what do we gain in the second example where we allocate a newbyte[]
in memory ?The specific example avoids allocations for strings that are less than 256 bytes, which might be enough of an optimization when dealing with small values. But like I said, nothing prevents you from extending the example to use buffer pooling.
Updated my WASM hosted app from preview 4 to 5. This occurs on startup:
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware: Error: An unhandled exception has occurred while executing the request.
System.InvalidOperationException: No output has been recevied from the application.
at Microsoft.AspNetCore.Builder.DebugProxyLauncher.LaunchAndGetUrl(IServiceProvider serviceProvider, String devToolsHost)
at Microsoft.AspNetCore.Builder.WebAssemblyNetDebugProxyAppBuilderExtensions.<>c.<b__0_1>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Builder.Extensions.MapMiddleware.InvokeCore(HttpContext context, PathString matchedPath, PathString remainingPath)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
I then created a new...
I get the same error attempting to debug Blazor Wasm in the browser, using Preview 5
Ad: Observability – I more and more miss some basic yet performant way to write logs to files (using ILogger) from Microsoft. Full blown logging frameworks are real overkill or bloatware.
What did you have in mind? I too sometimes don’t want to use a full blown logging framework and I turn to my personal logging library that has some handy overloads. Is there some specific functionality you’d have to build into your personal logging library that you’d rather get out of the box?
Angelos, what about this? https://github.com/dotnet/wpf/discussions/6542
We have read through all the comments in this post and acknowledge your feedback regarding the frustration in merging of PRs. Due to large number of integration tests which need to be run manually for each PR, you see a relatively long lead time for feedback. However, we are taking steps to address this and we are focused on modernizing our Test infrastructure to improve the turn-around times on PRs.
We are grateful for all your...
Good luck getting any kind of response with anything WPF related. Microsoft do not care about all of the existing LOB enterprise apps written in WPF, but they want everyone to embrace the ‘new’ stuff. The company with a 1.6TRILLION market cap won’t even staff the WPF team with a few devs to manage pull requests from the community. Pretty sad.
this comment has been deleted.
Why is there no
net7.0-linux
TFM?Because it is not needed. There are no APIs in .NET that only work on linux. E.g. the net7.0-windows is only needed on windows if you need to access windows specific APIs, like WPF. So you can just use net7.0 on Linux
Why Vs 2022 preview 4 is missing MAUI template?
Because you installed .NET 7 Preview. So much for side-by-side installation.
Thanks
nice to see community contributors getting the spotlight!
thanks for their contributions!
Great summary Angelos and a nice surprise to see your name here!
Hey Neil, what an awesome surprise. It’s been so long 🙂 I still think (and talk about) our times together constantly – robotic arms and web services … web services everywhere!
Thanks for the mention! Neil and I worked together as well, many moons back!